package com.pos.widgets.hlist;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.util.SparseArrayCompat;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.AbsListView;
import android.widget.Adapter;
import android.widget.Checkable;
import android.widget.ListAdapter;
import android.widget.ListView;

import com.pos.R;
import com.pos.widgets.hlist.util.ViewHelperFactory;
import com.pos.widgets.hlist.util.ViewHelperFactory.ViewHelper;
import com.pos.widgets.hlist.util.v11.MultiChoiceModeListener;
import com.pos.widgets.hlist.util.v11.MultiChoiceModeWrapper;

import java.util.ArrayList;
import java.util.List;

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public abstract class AbsHListView extends AdapterView<ListAdapter> implements
		ViewTreeObserver.OnGlobalLayoutListener,
		ViewTreeObserver.OnTouchModeChangeListener {

	private static final String TAG = "AbsListView";

	/**
	 * Disables the transcript mode.
	 * 
	 * @see #setTranscriptMode(int)
	 */
	public static final int TRANSCRIPT_MODE_DISABLED = 0;
	/**
	 * The list will automatically scroll to the bottom when a data set change
	 * notification is received and only if the last item is already visible on
	 * screen.
	 * 
	 * @see #setTranscriptMode(int)
	 */
	public static final int TRANSCRIPT_MODE_NORMAL = 1;
	/**
	 * The list will automatically scroll to the bottom, no matter what items
	 * are currently visible.
	 * 
	 * @see #setTranscriptMode(int)
	 */
	public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;

	/**
	 * Indicates that we are not in the middle of a touch gesture
	 */
	public static final int TOUCH_MODE_REST = -1;

	/**
	 * Indicates we just received the touch event and we are waiting to see if
	 * the it is a tap or a scroll gesture.
	 */
	public static final int TOUCH_MODE_DOWN = 0;

	/**
	 * Indicates the touch has been recognized as a tap and we are now waiting
	 * to see if the touch is a longpress
	 */
	public static final int TOUCH_MODE_TAP = 1;

	/**
	 * Indicates we have waited for everything we can wait for, but the user's
	 * finger is still down
	 */
	public static final int TOUCH_MODE_DONE_WAITING = 2;

	/**
	 * Indicates the touch gesture is a scroll
	 */
	public static final int TOUCH_MODE_SCROLL = 3;

	/**
	 * Indicates the view is in the process of being flung
	 */
	public static final int TOUCH_MODE_FLING = 4;

	/**
	 * Indicates the touch gesture is an overscroll - a scroll beyond the
	 * beginning or end.
	 */
	public static final int TOUCH_MODE_OVERSCROLL = 5;

	/**
	 * Indicates the view is being flung outside of normal content bounds and
	 * will spring back.
	 */
	public static final int TOUCH_MODE_OVERFLING = 6;

	/**
	 * Regular layout - usually an unsolicited layout from the view system
	 */
	public static final int LAYOUT_NORMAL = 0;

	/**
	 * Show the first item
	 */
	public static final int LAYOUT_FORCE_LEFT = 1;

	/**
	 * Force the selected item to be on somewhere on the screen
	 */
	public static final int LAYOUT_SET_SELECTION = 2;

	/**
	 * Show the last item
	 */
	public static final int LAYOUT_FORCE_RIGHT = 3;

	/**
	 * Make a mSelectedItem appear in a specific location and build the rest of
	 * the views from there. The top is specified by mSpecificLeft.
	 */
	public static final int LAYOUT_SPECIFIC = 4;

	/**
	 * Layout to sync as a result of a data change. Restore mSyncPosition to
	 * have its top at mSpecificLeft
	 */
	public static final int LAYOUT_SYNC = 5;

	/**
	 * Layout as a result of using the navigation keys
	 */
	public static final int LAYOUT_MOVE_SELECTION = 6;

	ViewHelper mViewHelper;

	/**
	 * Controls if/how the user may choose/check items in the list
	 */
	protected int mChoiceMode = ListView.CHOICE_MODE_NONE;

	/**
	 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
	 * 
	 * @hide
	 */
	public Object mChoiceActionMode;

	/**
	 * Wrapper for the multiple choice mode callback; AbsListView needs to
	 * perform a few extra actions around what application code does.
	 */
	Object mMultiChoiceModeCallback;

	/**
	 * Running count of how many items are currently checked
	 */
	int mCheckedItemCount;

	/**
	 * Running state of which positions are currently checked
	 */
	protected SparseArrayCompat<Boolean> mCheckStates;

	/**
	 * Running state of which IDs are currently checked. If there is a value for
	 * a given key, the checked state for that ID is true and the value holds
	 * the last known position in the adapter for that id.
	 */
	LongSparseArray<Integer> mCheckedIdStates;

	/**
	 * Controls how the next layout will happen
	 */
	protected int mLayoutMode = LAYOUT_NORMAL;

	/**
	 * Should be used by subclasses to listen to changes in the dataset
	 */
	protected AdapterDataSetObserver mDataSetObserver;

	/**
	 * The adapter containing the data to be displayed by this view
	 */
	protected ListAdapter mAdapter;

	/**
	 * If mAdapter != null, whenever this is true the adapter has stable IDs.
	 */
	boolean mAdapterHasStableIds;

	/**
	 * Indicates whether the list selector should be drawn on top of the
	 * children or behind
	 */
	boolean mDrawSelectorOnTop = false;

	/**
	 * The drawable used to draw the selector
	 */
	Drawable mSelector;

	/**
	 * The current position of the selector in the list.
	 */
	int mSelectorPosition = INVALID_POSITION;

	/**
	 * Defines the selector's location and dimension at drawing time
	 */
	protected Rect mSelectorRect = new Rect();

	/**
	 * The data set used to store unused views that should be reused during the
	 * next layout to avoid creating new ones
	 */
	protected final RecycleBin mRecycler = new RecycleBin();

	/**
	 * The selection's left padding
	 */
	int mSelectionLeftPadding = 0;

	/**
	 * The selection's top padding
	 */
	int mSelectionTopPadding = 0;

	/**
	 * The selection's right padding
	 */
	int mSelectionRightPadding = 0;

	/**
	 * The selection's bottom padding
	 */
	int mSelectionBottomPadding = 0;

	/**
	 * This view's padding
	 */
	protected Rect mListPadding = new Rect();

	/**
	 * Subclasses must retain their measure spec from onMeasure() into this
	 * member
	 */
	protected int mHeightMeasureSpec = 0;

	/**
	 * The top scroll indicator
	 */
	View mScrollLeft;

	/**
	 * The down scroll indicator
	 */
	View mScrollRight;

	/**
	 * When the view is scrolling, this flag is set to true to indicate
	 * subclasses that the drawing cache was enabled on the children
	 */
	protected boolean mCachingStarted;
	protected boolean mCachingActive;

	/**
	 * The position of the view that received the down motion event
	 */
	protected int mMotionPosition;

	/**
	 * The offset to the top of the mMotionPosition view when the down motion
	 * event was received
	 */
	int mMotionViewOriginalLeft;

	/**
	 * The desired offset to the top of the mMotionPosition view after a scroll
	 */
	int mMotionViewNewLeft;

	/**
	 * The X value associated with the the down motion event
	 */
	int mMotionX;

	/**
	 * The Y value associated with the the down motion event
	 */
	int mMotionY;

	/**
	 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP,
	 * TOUCH_MODE_SCROLL, or TOUCH_MODE_DONE_WAITING
	 */
	protected int mTouchMode = TOUCH_MODE_REST;

	/**
	 * Y value from on the previous motion event (if any)
	 */
	int mLastX;

	/**
	 * How far the finger moved before we started scrolling
	 */
	int mMotionCorrection;

	/**
	 * Determines speed during touch scrolling
	 */
	private VelocityTracker mVelocityTracker;

	/**
	 * Handles one frame of a fling
	 */
	private FlingRunnable mFlingRunnable;

	/**
	 * Handles scrolling between positions within the list.
	 */
	protected PositionScroller mPositionScroller;

	/**
	 * The offset in pixels form the top of the AdapterView to the top of the
	 * currently selected view. Used to save and restore state.
	 */
	protected int mSelectedLeft = 0;

	/**
	 * Indicates whether the list is stacked from the bottom edge or the top
	 * edge.
	 */
	protected boolean mStackFromRight;

	/**
	 * When set to true, the list automatically discards the children's bitmap
	 * cache after scrolling.
	 */
	boolean mScrollingCacheEnabled;

	/**
	 * Whether or not to enable the fast scroll feature on this list
	 */
	boolean mFastScrollEnabled;

	/**
	 * Optional callback to notify client when scroll position has changed
	 */
	private OnScrollListener mOnScrollListener;

	/**
	 * Indicates whether to use pixels-based or position-based scrollbar
	 * properties.
	 */
	private boolean mSmoothScrollbarEnabled = true;

	/**
	 * Rectangle used for hit testing children
	 */
	private Rect mTouchFrame;

	/**
	 * The position to resurrect the selected position to.
	 */
	protected int mResurrectToPosition = INVALID_POSITION;

	private ContextMenuInfo mContextMenuInfo = null;

	/**
	 * Maximum distance to record overscroll
	 */
	protected int mOverscrollMax;

	/**
	 * Content height divided by this is the overscroll limit.
	 */
	protected static final int OVERSCROLL_LIMIT_DIVISOR = 3;

	/**
	 * How many positions in either direction we will search to try to find a
	 * checked item with a stable ID that moved position across a data set
	 * change. If the item isn't found it will be unselected.
	 */
	private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;

	/**
	 * Used to request a layout when we changed touch mode
	 */
	private static final int TOUCH_MODE_UNKNOWN = -1;
	private static final int TOUCH_MODE_ON = 0;
	private static final int TOUCH_MODE_OFF = 1;

	private int mLastTouchMode = TOUCH_MODE_UNKNOWN;

	/**
	 * The last CheckForLongPress runnable we posted, if any
	 */
	private CheckForLongPress mPendingCheckForLongPress;

	/**
	 * The last CheckForTap runnable we posted, if any
	 */
	private Runnable mPendingCheckForTap;

	/**
	 * The last CheckForKeyLongPress runnable we posted, if any
	 */
	private CheckForKeyLongPress mPendingCheckForKeyLongPress;

	/**
	 * Acts upon click
	 */
	private AbsHListView.PerformClick mPerformClick;

	/**
	 * Delayed action for touch mode.
	 */
	private Runnable mTouchModeReset;

	/**
	 * This view is in transcript mode -- it shows the bottom of the list when
	 * the data changes
	 */
	private int mTranscriptMode;

	/**
	 * Indicates that this list is always drawn on top of a solid, single-color,
	 * opaque background
	 */
	private int mCacheColorHint;

	/**
	 * The select child's view (from the adapter's getView) is enabled.
	 */
	private boolean mIsChildViewEnabled;

	/**
	 * The last scroll state reported to clients through
	 * {@link OnScrollListener}.
	 */
	private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;

	private int mTouchSlop;

	private Runnable mClearScrollingCache;
	protected Runnable mPositionScrollAfterLayout;
	private int mMinimumVelocity;
	private int mMaximumVelocity;
	private float mVelocityScale = 1.0f;

	protected final boolean[] mIsScrap = new boolean[1];

	/**
	 * ID of the active pointer. This is used to retain consistency during
	 * drags/flings if multiple pointers are used.
	 */
	private int mActivePointerId = INVALID_POINTER;

	/**
	 * Sentinel value for no current active pointer. Used by
	 * {@link #mActivePointerId}.
	 */
	private static final int INVALID_POINTER = -1;

	/**
	 * Maximum distance to overscroll by during edge effects
	 */
	int mOverscrollDistance;

	/**
	 * Maximum distance to overfling during edge effects
	 */
	int mOverflingDistance;

	// These two EdgeGlows are always set and used together.
	// Checking one for null is as good as checking both.

	/**
	 * Tracks the state of the top edge glow.
	 */
	private EdgeEffectCompat mEdgeGlowTop;

	/**
	 * Tracks the state of the bottom edge glow.
	 */
	private EdgeEffectCompat mEdgeGlowBottom;

	/**
	 * An estimate of how many pixels are between the top of the list and the
	 * top of the first position in the adapter, based on the last time we saw
	 * it. Used to hint where to draw edge glows.
	 */
	private int mFirstPositionDistanceGuess;

	/**
	 * An estimate of how many pixels are between the bottom of the list and the
	 * bottom of the last position in the adapter, based on the last time we saw
	 * it. Used to hint where to draw edge glows.
	 */
	private int mLastPositionDistanceGuess;

	/**
	 * Used for determining when to cancel out of overscroll.
	 */
	private int mDirection = 0;

	/**
	 * Tracked on measurement in transcript mode. Makes sure that we can still
	 * pin to the bottom correctly on resizes.
	 */
	private boolean mForceTranscriptScroll;

	private int mGlowPaddingTop;
	private int mGlowPaddingBottom;

	/**
	 * Used for interacting with list items from an accessibility service.
	 */
	private ListItemAccessibilityDelegate mAccessibilityDelegate;

	private int mLastAccessibilityScrollEventFromIndex;
	private int mLastAccessibilityScrollEventToIndex;

	/**
	 * Track if we are currently attached to a window.
	 */
	protected boolean mIsAttached;

	/**
	 * Track the item count from the last time we handled a data change.
	 */
	private int mLastHandledItemCount;

	/**
	 * Used for smooth scrolling at a consistent rate
	 */
	static final Interpolator sLinearInterpolator = new LinearInterpolator();

	/**
	 * The saved state that we will be restoring from when we next sync. Kept
	 * here so that if we happen to be asked to save our state before the sync
	 * happens, we can return this existing data rather than losing it.
	 */
	private SavedState mPendingSync;

	/**
	 * Interface definition for a callback to be invoked when the list or grid
	 * has been scrolled.
	 */
	public interface OnScrollListener {

		/**
		 * The view is not scrolling. Note navigating the list using the
		 * trackball counts as being in the idle state since these transitions
		 * are not animated.
		 */
		public static int SCROLL_STATE_IDLE = 0;

		/**
		 * The user is scrolling using touch, and their finger is still on the
		 * screen
		 */
		public static int SCROLL_STATE_TOUCH_SCROLL = 1;

		/**
		 * The user had previously been scrolling using touch and had performed
		 * a fling. The animation is now coasting to a stop
		 */
		public static int SCROLL_STATE_FLING = 2;

		/**
		 * Callback method to be invoked while the list view or grid view is
		 * being scrolled. If the view is being scrolled, this method will be
		 * called before the next frame of the scroll is rendered. In
		 * particular, it will be called before any calls to
		 * {@link Adapter#getView(int, View, ViewGroup)}.
		 *
		 * @param view
		 *            The view whose scroll state is being reported
		 *
		 * @param scrollState
		 *            The current scroll state. One of
		 *            {@link #SCROLL_STATE_IDLE},
		 *            {@link #SCROLL_STATE_TOUCH_SCROLL} or
		 *            {@link #SCROLL_STATE_IDLE}.
		 */
		public void onScrollStateChanged(AbsHListView view, int scrollState);

		/**
		 * Callback method to be invoked when the list or grid has been
		 * scrolled. This will be called after the scroll has completed
		 *
		 * @param view
		 *            The view whose scroll state is being reported
		 * @param firstVisibleItem
		 *            the index of the first visible cell (ignore if
		 *            visibleItemCount == 0)
		 * @param visibleItemCount
		 *            the number of visible cells
		 * @param totalItemCount
		 *            the number of items in the list adaptor
		 */
		public void onScroll(AbsHListView view, int firstVisibleItem,
                             int visibleItemCount, int totalItemCount);
	}

	/**
	 * The top-level view of a list item can implement this interface to allow
	 * itself to modify the bounds of the selection shown for that item.
	 */
	public interface SelectionBoundsAdjuster {

		/**
		 * Called to allow the list item to adjust the bounds shown for its
		 * selection.
		 *
		 * @param bounds
		 *            On call, this contains the bounds the list has selected
		 *            for the item (that is the bounds of the entire view). The
		 *            values can be modified as desired.
		 */
		public void adjustListItemSelectionBounds(Rect bounds);
	}

	public AbsHListView(Context context) {
		super(context);
		initAbsListView();
	}

	public AbsHListView(Context context, AttributeSet attrs) {
		this(context, attrs, R.attr.hlv_absHListViewStyle);
	}

	public AbsHListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		if (LOG_ENABLED) {
			Log.i(LOG_TAG, "defStyle: " + defStyle);
		}

		initAbsListView();

		final Resources.Theme theme = context.getTheme();

		TypedArray array = theme.obtainStyledAttributes(attrs,
				R.styleable.AbsHListView, defStyle, 0);

		Drawable listSelector = null;
		boolean drawSelectorOnTop = false;
		boolean stackFromRight = false;
		boolean scrollingCacheEnabled = true;
		int transcriptMode = TRANSCRIPT_MODE_DISABLED;
		int color = 0;
		boolean smoothScrollbar = true;
		int choiceMode = AbsListView.CHOICE_MODE_NONE;

		if (null != array) {
			listSelector = array
					.getDrawable(R.styleable.AbsHListView_android_listSelector);
			drawSelectorOnTop = array.getBoolean(
					R.styleable.AbsHListView_android_drawSelectorOnTop, false);
			stackFromRight = array.getBoolean(
					R.styleable.AbsHListView_hlv_stackFromRight, false);
			scrollingCacheEnabled = array.getBoolean(
					R.styleable.AbsHListView_android_scrollingCache, true);
			transcriptMode = array.getInt(
					R.styleable.AbsHListView_hlv_transcriptMode,
					TRANSCRIPT_MODE_DISABLED);
			color = array.getColor(
					R.styleable.AbsHListView_android_cacheColorHint, 0);
			smoothScrollbar = array.getBoolean(
					R.styleable.AbsHListView_android_smoothScrollbar, true);
			choiceMode = array.getInt(
					R.styleable.AbsHListView_android_choiceMode,
					AbsListView.CHOICE_MODE_NONE);
			array.recycle();

			if (LOG_ENABLED) {
				Log.d(TAG, "listSelector: " + listSelector);
				Log.d(TAG, "drawSelectorOnTop: " + drawSelectorOnTop);
				Log.d(TAG, "stackFromRight: " + stackFromRight);
				Log.d(TAG, "scrollingCacheEnabled: " + scrollingCacheEnabled);
				Log.d(TAG, "transcriptMode: " + transcriptMode);
				Log.d(TAG, "smoothScrollbar: " + smoothScrollbar);
				Log.d(TAG, "choiceMode: " + choiceMode);
			}
		}

		if (listSelector != null) {
			setSelector(listSelector);
		}

		mDrawSelectorOnTop = drawSelectorOnTop;
		setStackFromRight(stackFromRight);
		setScrollingCacheEnabled(scrollingCacheEnabled);
		setTranscriptMode(transcriptMode);
		setCacheColorHint(color);
		setSmoothScrollbarEnabled(smoothScrollbar);
		setChoiceMode(choiceMode);
	}

	private void initAbsListView() {
		setClickable(true);
		setFocusableInTouchMode(true);
		setWillNotDraw(false);
		setAlwaysDrawnWithCacheEnabled(false);
		setScrollingCacheEnabled(true);

		final ViewConfiguration configuration = ViewConfiguration
				.get(getContext());
		mTouchSlop = configuration.getScaledTouchSlop();
		mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
		mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
		mOverscrollDistance = configuration.getScaledOverscrollDistance();
		mOverflingDistance = configuration.getScaledOverflingDistance();
		mViewHelper = ViewHelperFactory.create(this);
	}

	@Override
	public void setOverScrollMode(int mode) {
		if (mode != OVER_SCROLL_NEVER) {
			if (mEdgeGlowTop == null) {
				Context context = getContext();
				mEdgeGlowTop = new EdgeEffectCompat(context);
				mEdgeGlowBottom = new EdgeEffectCompat(context);
			}
		} else {
			mEdgeGlowTop = null;
			mEdgeGlowBottom = null;
		}
		super.setOverScrollMode(mode);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setAdapter(ListAdapter adapter) {
		if (adapter != null) {
			mAdapterHasStableIds = mAdapter.hasStableIds();
			if (mChoiceMode != AbsListView.CHOICE_MODE_NONE
					&& mAdapterHasStableIds && mCheckedIdStates == null) {
				mCheckedIdStates = new LongSparseArray<Integer>();
			}
		}

		if (mCheckStates != null) {
			mCheckStates.clear();
		}

		if (mCheckedIdStates != null) {
			mCheckedIdStates.clear();
		}
	}

	/**
	 * Returns the number of items currently selected. This will only be valid
	 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
	 *
	 * <p>
	 * To determine the specific items that are currently selected, use one of
	 * the <code>getChecked*</code> methods.
	 *
	 * @return The number of items currently selected
	 *
	 * @see #getCheckedItemPosition()
	 * @see #getCheckedItemPositions()
	 * @see #getCheckedItemIds()
	 */
	public int getCheckedItemCount() {
		return mCheckedItemCount;
	}

	/**
	 * Returns the checked state of the specified position. The result is only
	 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
	 * {@link #CHOICE_MODE_MULTIPLE}.
	 *
	 * @param position
	 *            The item whose checked state to return
	 * @return The item's checked state or <code>false</code> if choice mode is
	 *         invalid
	 *
	 * @see #setChoiceMode(int)
	 */
	public boolean isItemChecked(int position) {
		if (mChoiceMode != AbsListView.CHOICE_MODE_NONE && mCheckStates != null) {
			return mCheckStates.get(position, false);
		}

		return false;
	}

	/**
	 * Returns the currently checked item. The result is only valid if the
	 * choice mode has been set to {@link #CHOICE_MODE_SINGLE}.
	 *
	 * @return The position of the currently checked item or
	 *         {@link #INVALID_POSITION} if nothing is selected
	 *
	 * @see #setChoiceMode(int)
	 */
	public int getCheckedItemPosition() {
		if (mChoiceMode == AbsListView.CHOICE_MODE_SINGLE
				&& mCheckStates != null && mCheckStates.size() == 1) {
			return mCheckStates.keyAt(0);
		}

		return INVALID_POSITION;
	}

	/**
	 * Returns the set of checked items in the list. The result is only valid if
	 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
	 *
	 * @return A SparseBooleanArray which will return true for each call to
	 *         get(int position) where position is a position in the list, or
	 *         <code>null</code> if the choice mode is set to
	 *         {@link #CHOICE_MODE_NONE}.
	 */
	public SparseArrayCompat<Boolean> getCheckedItemPositions() {
		if (mChoiceMode != AbsListView.CHOICE_MODE_NONE) {
			return mCheckStates;
		}
		return null;
	}

	/**
	 * Returns the set of checked items ids. The result is only valid if the
	 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
	 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
	 *
	 * @return A new array which contains the id of each checked item in the
	 *         list.
	 */
	public long[] getCheckedItemIds() {
		if (mChoiceMode == AbsListView.CHOICE_MODE_NONE
				|| mCheckedIdStates == null || mAdapter == null) {
			return new long[0];
		}

		final LongSparseArray<Integer> idStates = mCheckedIdStates;
		final int count = idStates.size();
		final long[] ids = new long[count];

		for (int i = 0; i < count; i++) {
			ids[i] = idStates.keyAt(i);
		}

		return ids;
	}

	/**
	 * Clear any choices previously set
	 */
	public void clearChoices() {
		if (mCheckStates != null) {
			mCheckStates.clear();
		}
		if (mCheckedIdStates != null) {
			mCheckedIdStates.clear();
		}
		mCheckedItemCount = 0;
	}

	/**
	 * Sets the checked state of the specified position. The is only valid if
	 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
	 * {@link #CHOICE_MODE_MULTIPLE}.
	 *
	 * @param position
	 *            The item whose checked state is to be checked
	 * @param value
	 *            The new checked state for the item
	 */
	public void setItemChecked(int position, boolean value) {
		if (mChoiceMode == AbsListView.CHOICE_MODE_NONE) {
			return;
		}

		// Start selection mode if needed. We don't need to if we're unchecking
		// something.
		if (android.os.Build.VERSION.SDK_INT >= 11) {
			if (value && mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE_MODAL
					&& mChoiceActionMode == null) {
				if (mMultiChoiceModeCallback == null
						|| !((MultiChoiceModeWrapper) mMultiChoiceModeCallback)
								.hasWrappedCallback()) {
					throw new IllegalStateException(
							"AbsListView: attempted to start selection mode "
									+ "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was "
									+ "supplied. Call setMultiChoiceModeListener to set a callback.");
				}
				mChoiceActionMode = startActionMode((MultiChoiceModeWrapper) mMultiChoiceModeCallback);
			}
		}

		if (mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE
				|| (android.os.Build.VERSION.SDK_INT >= 11 && mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE_MODAL)) {
			boolean oldValue = mCheckStates.get(position, false);
			mCheckStates.put(position, value);
			if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
				if (value) {
					mCheckedIdStates
							.put(mAdapter.getItemId(position), position);
				} else {
					mCheckedIdStates.delete(mAdapter.getItemId(position));
				}
			}
			if (oldValue != value) {
				if (value) {
					mCheckedItemCount++;
				} else {
					mCheckedItemCount--;
				}
			}
			if (mChoiceActionMode != null) {
				final long id = mAdapter.getItemId(position);
				((MultiChoiceModeWrapper) mMultiChoiceModeCallback)
						.onItemCheckedStateChanged(
								(ActionMode) mChoiceActionMode, position, id,
								value);
			}
		} else {
			boolean updateIds = mCheckedIdStates != null
					&& mAdapter.hasStableIds();
			// Clear all values if we're checking something, or unchecking the
			// currently
			// selected item
			if (value || isItemChecked(position)) {
				mCheckStates.clear();
				if (updateIds) {
					mCheckedIdStates.clear();
				}
			}
			// this may end up selecting the value we just cleared but this way
			// we ensure length of mCheckStates is 1, a fact
			// getCheckedItemPosition relies on
			if (value) {
				mCheckStates.put(position, true);
				if (updateIds) {
					mCheckedIdStates
							.put(mAdapter.getItemId(position), position);
				}
				mCheckedItemCount = 1;
			} else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
				mCheckedItemCount = 0;
			}
		}

		// Do not generate a data change while we are in the layout phase
		if (!mInLayout && !mBlockLayoutRequests) {
			mDataChanged = true;
			rememberSyncState();
			requestLayout();
		}
	}

	@Override
	public boolean performItemClick(View view, int position, long id) {
		boolean handled = false;
		boolean dispatchItemClick = true;

		if (mChoiceMode != AbsListView.CHOICE_MODE_NONE) {
			handled = true;
			boolean checkedStateChanged = false;

			if (mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE
					|| (android.os.Build.VERSION.SDK_INT >= 11
							&& mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
				boolean checked = !mCheckStates.get(position, false);
				mCheckStates.put(position, checked);
				if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
					if (checked) {
						mCheckedIdStates.put(mAdapter.getItemId(position),
								position);
					} else {
						mCheckedIdStates.delete(mAdapter.getItemId(position));
					}
				}
				if (checked) {
					mCheckedItemCount++;
				} else {
					mCheckedItemCount--;
				}

				if (mChoiceActionMode != null) {
					((MultiChoiceModeWrapper) mMultiChoiceModeCallback)
							.onItemCheckedStateChanged(
									(ActionMode) mChoiceActionMode, position,
									id, checked);
					dispatchItemClick = false;
				}

				checkedStateChanged = true;
			} else if (mChoiceMode == AbsListView.CHOICE_MODE_SINGLE) {
				boolean checked = !mCheckStates.get(position, false);
				if (checked) {
					mCheckStates.clear();
					mCheckStates.put(position, true);
					if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
						mCheckedIdStates.clear();
						mCheckedIdStates.put(mAdapter.getItemId(position),
								position);
					}
					mCheckedItemCount = 1;
				} else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
					mCheckedItemCount = 0;
				}
				checkedStateChanged = true;
			}

			if (checkedStateChanged) {
				updateOnScreenCheckedViews();
			}
		}

		if (dispatchItemClick) {
			handled |= super.performItemClick(view, position, id);
		}

		return handled;
	}

	/**
	 * Perform a quick, in-place update of the checked or activated state on all
	 * visible item views. This should only be called when a valid choice mode
	 * is active.
	 */
	private void updateOnScreenCheckedViews() {
		final int firstPos = mFirstPosition;
		final int count = getChildCount();
		final boolean useActivated = android.os.Build.VERSION.SDK_INT >= 11;
		for (int i = 0; i < count; i++) {
			final View child = getChildAt(i);
			final int position = firstPos + i;

			if (child instanceof Checkable) {
				((Checkable) child).setChecked(mCheckStates
						.get(position, false));
			} else if (useActivated) {
				child.setActivated(mCheckStates.get(position, false));
			}
		}
	}

	/**
	 * @see #setChoiceMode(int)
	 *
	 * @return The current choice mode
	 */
	public int getChoiceMode() {
		return mChoiceMode;
	}

	/**
	 * Defines the choice behavior for the List. By default, Lists do not have
	 * any choice behavior ({@link #CHOICE_MODE_NONE}). By setting the
	 * choiceMode to {@link #CHOICE_MODE_SINGLE}, the List allows up to one item
	 * to be in a chosen state. By setting the choiceMode to
	 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be
	 * chosen.
	 *
	 * @param choiceMode
	 *            One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE},
	 *            or {@link #CHOICE_MODE_MULTIPLE}
	 */
	@TargetApi(11)
	public void setChoiceMode(int choiceMode) {
		mChoiceMode = choiceMode;

		if (android.os.Build.VERSION.SDK_INT >= 11) {
			if (mChoiceActionMode != null) {

				if (android.os.Build.VERSION.SDK_INT >= 11) {
					((ActionMode) mChoiceActionMode).finish();
				}
				mChoiceActionMode = null;
			}
		}

		if (mChoiceMode != AbsListView.CHOICE_MODE_NONE) {
			if (mCheckStates == null) {
				mCheckStates = new SparseArrayCompat<Boolean>();
			}
			if (mCheckedIdStates == null && mAdapter != null
					&& mAdapter.hasStableIds()) {
				mCheckedIdStates = new LongSparseArray<Integer>();
			}
			// Modal multi-choice mode only has choices when the mode is active.
			// Clear them.
			if (android.os.Build.VERSION.SDK_INT >= 11) {
				if (mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE_MODAL) {
					clearChoices();
					setLongClickable(true);
				}
			}
		}
	}

	/**
	 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of
	 * the selection {@link ActionMode}. Only used when the choice mode is set
	 * to {@link #CHOICE_MODE_MULTIPLE_MODAL}.
	 *
	 * @param listener
	 *            Listener that will manage the selection mode
	 *
	 * @see #setChoiceMode(int)
	 */
	@TargetApi(11)
	public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
		if (android.os.Build.VERSION.SDK_INT >= 11) {
			if (mMultiChoiceModeCallback == null) {
				mMultiChoiceModeCallback = new MultiChoiceModeWrapper(this);
			}
			((MultiChoiceModeWrapper) mMultiChoiceModeCallback)
					.setWrapped(listener);
		} else {
			Log.e(TAG,
					"setMultiChoiceModeListener not supported for this version of Android");
		}
	}

	/**
	 * @return true if all list content currently fits within the view
	 *         boundaries
	 */
	private boolean contentFits() {
		final int childCount = getChildCount();
		if (childCount == 0)
			return true;
		if (childCount != mItemCount)
			return false;

		return getChildAt(0).getLeft() >= mListPadding.left
				&& getChildAt(childCount - 1).getRight() <= getWidth()
						- mListPadding.right;
	}

	@Override
	protected int getHorizontalScrollbarHeight() {
		return super.getHorizontalScrollbarHeight();
	}

	/**
	 * When smooth scrollbar is enabled, the position and size of the scrollbar
	 * thumb is computed based on the number of visible pixels in the visible
	 * items. This however assumes that all list items have the same height. If
	 * you use a list in which items have different heights, the scrollbar will
	 * change appearance as the user scrolls through the list. To avoid this
	 * issue, you need to disable this property.
	 *
	 * When smooth scrollbar is disabled, the position and size of the scrollbar
	 * thumb is based solely on the number of items in the adapter and the
	 * position of the visible items inside the adapter. This provides a stable
	 * scrollbar as the user navigates through a list of items with varying
	 * heights.
	 *
	 * @param enabled
	 *            Whether or not to enable smooth scrollbar.
	 *
	 * @see #setSmoothScrollbarEnabled(boolean)
	 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
	 */
	public void setSmoothScrollbarEnabled(boolean enabled) {
		mSmoothScrollbarEnabled = enabled;
	}

	/**
	 * Returns the current state of the fast scroll feature.
	 *
	 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
	 *
	 * @see #setSmoothScrollbarEnabled(boolean)
	 */
	@ViewDebug.ExportedProperty
	public boolean isSmoothScrollbarEnabled() {
		return mSmoothScrollbarEnabled;
	}

	/**
	 * Set the listener that will receive notifications every time the list
	 * scrolls.
	 *
	 * @param l
	 *            the scroll listener
	 */
	public void setOnScrollListener(OnScrollListener l) {
		mOnScrollListener = l;
		invokeOnItemScrollListener();
	}

	/**
	 * Notify our scroll listener (if there is one) of a change in scroll state
	 */
	protected void invokeOnItemScrollListener() {
		if (mOnScrollListener != null) {
			mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(),
					mItemCount);
		}
		onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does
										// not use these.
	}

	@Override
	public void sendAccessibilityEvent(int eventType) {
		// Since this class calls onScrollChanged even if the mFirstPosition and
		// the
		// child count have not changed we will avoid sending duplicate
		// accessibility
		// events.
		if (eventType == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
			final int firstVisiblePosition = getFirstVisiblePosition();
			final int lastVisiblePosition = getLastVisiblePosition();
			if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
					&& mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
				return;
			} else {
				mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
				mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
			}
		}
		super.sendAccessibilityEvent(eventType);
	}

	@TargetApi(14)
	@Override
	public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
		super.onInitializeAccessibilityEvent(event);
		event.setClassName(AbsHListView.class.getName());
	}

	@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
	@SuppressLint("Override")
	@Override
	public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
		super.onInitializeAccessibilityNodeInfo(info);

		info.setClassName(AbsHListView.class.getName());
		if (isEnabled()) {
			if (getFirstVisiblePosition() > 0) {
				info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
			}
			if (getLastVisiblePosition() < getCount() - 1) {
				info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
			}
		}
	}

	@TargetApi(16)
	@Override
	public boolean performAccessibilityAction(int action, Bundle arguments) {
		if (super.performAccessibilityAction(action, arguments)) {
			return true;
		}
		switch (action) {
		case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
			if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
				final int viewportWidth = getWidth() - mListPadding.left
						- mListPadding.right;
				smoothScrollBy(viewportWidth, PositionScroller.SCROLL_DURATION);
				return true;
			}
		}
			return false;
		case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
			if (isEnabled() && mFirstPosition > 0) {
				final int viewportWidth = getWidth() - mListPadding.left
						- mListPadding.right;
				smoothScrollBy(-viewportWidth, PositionScroller.SCROLL_DURATION);
				return true;
			}
		}
			return false;
		}
		return false;
	}

	/**
	 * Indicates whether the children's drawing cache is used during a scroll.
	 * By default, the drawing cache is enabled but this will consume more
	 * memory.
	 *
	 * @return true if the scrolling cache is enabled, false otherwise
	 *
	 * @see #setScrollingCacheEnabled(boolean)
	 * @see View#setDrawingCacheEnabled(boolean)
	 */
	@ViewDebug.ExportedProperty
	public boolean isScrollingCacheEnabled() {
		return mScrollingCacheEnabled;
	}

	/**
	 * Enables or disables the children's drawing cache during a scroll. By
	 * default, the drawing cache is enabled but this will use more memory.
	 *
	 * When the scrolling cache is enabled, the caches are kept after the first
	 * scrolling. You can manually clear the cache by calling
	 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
	 *
	 * @param enabled
	 *            true to enable the scroll cache, false otherwise
	 *
	 * @see #isScrollingCacheEnabled()
	 * @see View#setDrawingCacheEnabled(boolean)
	 */
	public void setScrollingCacheEnabled(boolean enabled) {
		if (mScrollingCacheEnabled && !enabled) {
			clearScrollingCache();
		}
		mScrollingCacheEnabled = enabled;
	}

	@Override
	public void getFocusedRect(Rect r) {
		View view = getSelectedView();
		if (view != null && view.getParent() == this) {
			// the focused rectangle of the selected view offset into the
			// coordinate space of this view.
			view.getFocusedRect(r);
			offsetDescendantRectToMyCoords(view, r);
		} else {
			// otherwise, just the norm
			super.getFocusedRect(r);
		}
	}

	private void useDefaultSelector() {
		setSelector(getResources().getDrawable(
				android.R.drawable.list_selector_background));
	}

	/**
	 * Indicates whether the content of this view is pinned to, or stacked from,
	 * the bottom edge.
	 *
	 * @return true if the content is stacked from the bottom edge, false
	 *         otherwise
	 */
	public boolean isStackFromRight() {
		return mStackFromRight;
	}

	/**
	 * When stack from bottom is set to true, the list fills its content
	 * starting from the bottom of the view.
	 *
	 * @param stackFromBottom
	 *            true to pin the view's content to the bottom edge, false to
	 *            pin the view's content to the top edge
	 */
	public void setStackFromRight(boolean stackFromRight) {
		if (mStackFromRight != stackFromRight) {
			mStackFromRight = stackFromRight;
			requestLayoutIfNecessary();
		}
	}

	void requestLayoutIfNecessary() {
		if (getChildCount() > 0) {
			resetList();
			requestLayout();
			invalidate();
		}
	}

	static class SavedState extends BaseSavedState {

		long selectedId;
		long firstId;
		int viewLeft;
		int position;
		int width;
		String filter;
		boolean inActionMode;
		int checkedItemCount;
		SparseArrayCompat<Boolean> checkState;
		LongSparseArray<Integer> checkIdState;

		/**
		 * Constructor called from {@link AbsHListView#onSaveInstanceState()}
		 */
		SavedState(Parcelable superState) {
			super(superState);
		}

		/**
		 * Constructor called from {@link #CREATOR}
		 */
		private SavedState(Parcel in) {
			super(in);
			selectedId = in.readLong();
			firstId = in.readLong();
			viewLeft = in.readInt();
			position = in.readInt();
			width = in.readInt();
			filter = in.readString();
			inActionMode = in.readByte() != 0;
			checkedItemCount = in.readInt();
			checkState = readSparseBooleanArray(in);
			checkIdState = readSparseLongArray(in);
		}

		private LongSparseArray<Integer> readSparseLongArray(Parcel in) {
			if (LOG_ENABLED) {
				Log.i(TAG, "readSparseLongArray");
			}
			final int N = in.readInt();
			if (N <= 0) {
				return null;
			}
			LongSparseArray<Integer> array = new LongSparseArray<Integer>(N);
			readSparseLongArrayInternal(array, in, N);
			return array;
		}

		private SparseArrayCompat<Boolean> readSparseBooleanArray(Parcel in) {
			if (LOG_ENABLED) {
				Log.i(TAG, "readSparseBooleanArray");
			}
			int N = in.readInt();
			if (N < 0) {
				return null;
			}
			SparseArrayCompat<Boolean> sa = new SparseArrayCompat<Boolean>(N);
			readSparseBooleanArrayInternal(sa, in, N);
			return sa;
		}

		private void readSparseLongArrayInternal(
                LongSparseArray<Integer> outVal, Parcel in, int N) {
			while (N > 0) {
				final long key = in.readLong();
				final int value = in.readInt();
				if (LOG_ENABLED) {
					Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
				}
				outVal.put(key, value);
				N--;
			}
		}

		private void readSparseBooleanArrayInternal(
                SparseArrayCompat<Boolean> outVal, Parcel in, int N) {
			while (N > 0) {
				int key = in.readInt();
				boolean value = in.readByte() == 1;
				if (LOG_ENABLED) {
					Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
				}
				outVal.append(key, value);
				N--;
			}
		}

		private void writeSparseLongArray(LongSparseArray<Integer> array,
				Parcel out) {
			if (LOG_ENABLED) {
				Log.i(TAG, "writeSparseLongArray");
			}
			final int N = array != null ? array.size() : 0;
			out.writeInt(N);
			for (int i = 0; i < N; i++) {
				out.writeLong(array.keyAt(i));
				out.writeInt(array.valueAt(i));
			}
		}

		private void writeSparseBooleanArray(SparseArrayCompat<Boolean> val,
				Parcel out) {
			if (LOG_ENABLED) {
				Log.i(TAG, "writeSparseBooleanArray");
			}
			if (val == null) {
				out.writeInt(-1);
				return;
			}
			int N = val.size();
			out.writeInt(N);
			int i = 0;
			while (i < N) {
				out.writeInt(val.keyAt(i));
				out.writeByte((byte) (val.valueAt(i) ? 1 : 0));
				i++;
			}
		}

		@Override
		public void writeToParcel(Parcel out, int flags) {
			if (LOG_ENABLED) {
				Log.i(TAG, "writeToParcel");
			}
			super.writeToParcel(out, flags);
			out.writeLong(selectedId);
			out.writeLong(firstId);
			out.writeInt(viewLeft);
			out.writeInt(position);
			out.writeInt(width);
			out.writeString(filter);
			out.writeByte((byte) (inActionMode ? 1 : 0));
			out.writeInt(checkedItemCount);

			if (LOG_ENABLED) {
				Log.d(TAG, "writing checkState: " + checkState);
				Log.d(TAG, "writing checkIdState: " + checkIdState);
			}

			writeSparseBooleanArray(checkState, out);
			writeSparseLongArray(checkIdState, out);
		}

		@Override
		public String toString() {
			return "AbsListView.SavedState{"
					+ Integer.toHexString(System.identityHashCode(this))
					+ " selectedId=" + selectedId + " firstId=" + firstId
					+ " viewLeft=" + viewLeft + " position=" + position
					+ " width=" + width + " filter=" + filter + " checkState="
					+ checkState + "}";
		}

		public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {

			@Override
			public SavedState createFromParcel(Parcel in) {
				return new SavedState(in);
			}

			@Override
			public SavedState[] newArray(int size) {
				return new SavedState[size];
			}
		};
	}

	@Override
	public Parcelable onSaveInstanceState() {
		if (LOG_ENABLED) {
			Log.i(TAG, "onSaveInstanceState");
			Log.d(TAG, "mPendingSync: " + mPendingSync);
		}
		/*
		 * This doesn't really make sense as the place to dismiss the popups,
		 * but there don't seem to be any other useful hooks that happen early
		 * enough to keep from getting complaints about having leaked the
		 * window.
		 */
		Parcelable superState = super.onSaveInstanceState();

		SavedState ss = new SavedState(superState);

		if (mPendingSync != null) {
			// Just keep what we last restored.
			ss.selectedId = mPendingSync.selectedId;
			ss.firstId = mPendingSync.firstId;
			ss.viewLeft = mPendingSync.viewLeft;
			ss.position = mPendingSync.position;
			ss.width = mPendingSync.width;
			ss.filter = mPendingSync.filter;
			ss.inActionMode = mPendingSync.inActionMode;
			ss.checkedItemCount = mPendingSync.checkedItemCount;
			ss.checkState = mPendingSync.checkState;
			ss.checkIdState = mPendingSync.checkIdState;
			return ss;
		}

		boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
		long selectedId = getSelectedItemId();
		ss.selectedId = selectedId;
		ss.width = getWidth();

		if (selectedId >= 0) {
			// Remember the selection
			ss.viewLeft = mSelectedLeft;
			ss.position = getSelectedItemPosition();
			ss.firstId = INVALID_POSITION;
		} else {
			if (haveChildren && mFirstPosition > 0) {
				// Remember the position of the first child.
				// We only do this if we are not currently at the top of
				// the list, for two reasons:
				// (1) The list may be in the process of becoming empty, in
				// which case mItemCount may not be 0, but if we try to
				// ask for any information about position 0 we will crash.
				// (2) Being "at the top" seems like a special case, anyway,
				// and the user wouldn't expect to end up somewhere else when
				// they revisit the list even if its content has changed.
				View v = getChildAt(0);
				ss.viewLeft = v.getLeft();
				int firstPos = mFirstPosition;
				if (firstPos >= mItemCount) {
					firstPos = mItemCount - 1;
				}
				ss.position = firstPos;
				ss.firstId = mAdapter.getItemId(firstPos);
			} else {
				ss.viewLeft = 0;
				ss.firstId = INVALID_POSITION;
				ss.position = 0;
			}
		}

		ss.filter = null;
		ss.inActionMode = android.os.Build.VERSION.SDK_INT >= 11
				&& mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE_MODAL
				&& mChoiceActionMode != null;

		if (mCheckStates != null) {
			try {
				ss.checkState = mCheckStates.clone();
			} catch (NoSuchMethodError e) {
				e.printStackTrace();
				ss.checkState = new SparseArrayCompat<Boolean>();
			}
		}
		if (mCheckedIdStates != null) {
			final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
			final int count = mCheckedIdStates.size();
			for (int i = 0; i < count; i++) {
				idState.put(mCheckedIdStates.keyAt(i),
						mCheckedIdStates.valueAt(i));
			}
			ss.checkIdState = idState;
		}
		ss.checkedItemCount = mCheckedItemCount;

		return ss;
	}

	@Override
	public void onRestoreInstanceState(Parcelable state) {
		if (LOG_ENABLED) {
			Log.i(TAG, "onRestoreInstanceState");
		}
		SavedState ss = (SavedState) state;

		super.onRestoreInstanceState(ss.getSuperState());
		mDataChanged = true;

		mSyncWidth = ss.width;

		if (ss.selectedId >= 0) {
			mNeedSync = true;
			mPendingSync = ss;
			mSyncColId = ss.selectedId;
			mSyncPosition = ss.position;
			mSpecificLeft = ss.viewLeft;
			mSyncMode = SYNC_SELECTED_POSITION;
		} else if (ss.firstId >= 0) {
			setSelectedPositionInt(INVALID_POSITION);
			// Do this before setting mNeedSync since setNextSelectedPosition
			// looks at mNeedSync
			setNextSelectedPositionInt(INVALID_POSITION);
			mSelectorPosition = INVALID_POSITION;
			mNeedSync = true;
			mPendingSync = ss;
			mSyncColId = ss.firstId;
			mSyncPosition = ss.position;
			mSpecificLeft = ss.viewLeft;
			mSyncMode = SYNC_FIRST_POSITION;
		}

		if (LOG_ENABLED) {
			Log.d(TAG, "checkState: " + ss.checkState);
			Log.d(TAG, "checkIdState: " + ss.checkIdState);
		}

		if (ss.checkState != null) {
			mCheckStates = ss.checkState;
		}

		if (ss.checkIdState != null) {
			mCheckedIdStates = ss.checkIdState;
		}

		mCheckedItemCount = ss.checkedItemCount;

		if (android.os.Build.VERSION.SDK_INT >= 11) {
			if (ss.inActionMode
					&& mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE_MODAL
					&& mMultiChoiceModeCallback != null) {
				mChoiceActionMode = startActionMode((MultiChoiceModeWrapper) mMultiChoiceModeCallback);
			}
		}

		requestLayout();
	}

	@Override
	protected void onFocusChanged(boolean gainFocus, int direction,
			Rect previouslyFocusedRect) {
		super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
		if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
			if (!mIsAttached && mAdapter != null) {
				// Data may have changed while we were detached and it's valid
				// to change focus while detached. Refresh so we don't die.
				mDataChanged = true;
				mOldItemCount = mItemCount;
				mItemCount = mAdapter.getCount();
			}
			resurrectSelection();
		}
	}

	@Override
	public void requestLayout() {
		if (!mBlockLayoutRequests && !mInLayout) {
			super.requestLayout();
		}
	}

	/**
	 * The list is empty. Clear everything out.
	 */
	protected void resetList() {
		removeAllViewsInLayout();
		mFirstPosition = 0;
		mDataChanged = false;
		mPositionScrollAfterLayout = null;
		mNeedSync = false;
		mPendingSync = null;
		mOldSelectedPosition = INVALID_POSITION;
		mOldSelectedColId = INVALID_COL_ID;
		setSelectedPositionInt(INVALID_POSITION);
		setNextSelectedPositionInt(INVALID_POSITION);
		mSelectedLeft = 0;
		mSelectorPosition = INVALID_POSITION;
		mSelectorRect.setEmpty();
		invalidate();
	}

	@Override
	protected int computeHorizontalScrollExtent() {
		final int count = getChildCount();
		if (count > 0) {
			if (mSmoothScrollbarEnabled) {
				int extent = count * 100;

				View view = getChildAt(0);
				final int left = view.getLeft();
				int width = view.getWidth();
				if (width > 0) {
					extent += (left * 100) / width;
				}

				view = getChildAt(count - 1);
				final int right = view.getRight();
				width = view.getWidth();
				if (width > 0) {
					extent -= ((right - getWidth()) * 100) / width;
				}

				return extent;
			} else {
				return 1;
			}
		}
		return 0;
	}

	@Override
	protected int computeHorizontalScrollOffset() {
		final int firstPosition = mFirstPosition;
		final int childCount = getChildCount();
		if (firstPosition >= 0 && childCount > 0) {
			if (mSmoothScrollbarEnabled) {
				final View view = getChildAt(0);
				final int left = view.getLeft();
				int width = view.getWidth();
				if (width > 0) {
					return Math.max(firstPosition
							* 100
							- (left * 100)
							/ width
							+ (int) ((float) getScrollX() / getWidth()
									* mItemCount * 100), 0);
				}
			} else {
				int index;
				final int count = mItemCount;
				if (firstPosition == 0) {
					index = 0;
				} else if (firstPosition + childCount == count) {
					index = count;
				} else {
					index = firstPosition + childCount / 2;
				}
				return (int) (firstPosition + childCount
						* (index / (float) count));
			}
		}
		return 0;
	}

	@Override
	protected int computeHorizontalScrollRange() {
		int result;
		if (mSmoothScrollbarEnabled) {
			result = Math.max(mItemCount * 100, 0);
			if (getScrollX() != 0) {
				// Compensate for overscroll
				result += Math.abs((int) ((float) getScrollX() / getWidth()
						* mItemCount * 100));
			}
		} else {
			result = mItemCount;
		}
		return result;
	}

	@Override
	protected float getLeftFadingEdgeStrength() {
		final int count = getChildCount();
		final float fadeEdge = super.getLeftFadingEdgeStrength();
		if (count == 0) {
			return fadeEdge;
		} else {
			if (mFirstPosition > 0) {
				return 1.0f;
			}

			final int left = getChildAt(0).getLeft();
			final float fadeLength = getHorizontalFadingEdgeLength();
			return left < getPaddingLeft() ? -(left - getPaddingLeft())
					/ fadeLength : fadeEdge;
		}
	}

	@Override
	protected float getRightFadingEdgeStrength() {
		final int count = getChildCount();
		final float fadeEdge = super.getRightFadingEdgeStrength();
		if (count == 0) {
			return fadeEdge;
		} else {
			if (mFirstPosition + count - 1 < mItemCount - 1) {
				return 1.0f;
			}

			final int right = getChildAt(count - 1).getRight();
			final int width = getWidth();
			final float fadeLength = getHorizontalFadingEdgeLength();
			return right > width - getPaddingRight() ? (right - width + getPaddingRight())
					/ fadeLength
					: fadeEdge;
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		if (mSelector == null) {
			useDefaultSelector();
		}
		final Rect listPadding = mListPadding;
		listPadding.left = mSelectionLeftPadding + getPaddingLeft();
		listPadding.top = mSelectionTopPadding + getPaddingTop();
		listPadding.right = mSelectionRightPadding + getPaddingRight();
		listPadding.bottom = mSelectionBottomPadding + getPaddingBottom();

		// Check if our previous measured size was at a point where we should
		// scroll later.
		if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
			final int childCount = getChildCount();
			final int listRight = getWidth() - getPaddingRight();
			final View lastChild = getChildAt(childCount - 1);
			final int lastRight = lastChild != null ? lastChild.getRight()
					: listRight;
			mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount
					&& lastRight <= listRight;
		}
	}

	/**
	 * Subclasses should NOT override this method but {@link #layoutChildren()}
	 * instead.
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		mInLayout = true;
		if (changed) {
			int childCount = getChildCount();
			for (int i = 0; i < childCount; i++) {
				getChildAt(i).forceLayout();
			}
			mRecycler.markChildrenDirty();
		}

		layoutChildren();
		mInLayout = false;

		mOverscrollMax = (r - l) / OVERSCROLL_LIMIT_DIVISOR;
	}

	/*
	 * @Override protected boolean setFrame( int left, int top, int right, int
	 * bottom ) { final boolean changed = super.setFrame( left, top, right,
	 * bottom );
	 *
	 * if ( changed ) { // Reposition the popup when the frame has changed. This
	 * includes // translating the widget, not just changing its dimension. The
	 * // filter popup needs to follow the widget. final boolean visible =
	 * getWindowVisibility() == View.VISIBLE; if ( mFiltered && visible &&
	 * mPopup != null && mPopup.isShowing() ) { positionPopup(); } }
	 *
	 * return changed; }
	 */

	/**
	 * Subclasses must override this method to layout their children.
	 */
	protected void layoutChildren() {
	}

	protected void updateScrollIndicators() {
		if (mScrollLeft != null) {
			boolean canScrollLeft;
			// 0th element is not visible
			canScrollLeft = mFirstPosition > 0;

			// ... Or top of 0th element is not visible
			if (!canScrollLeft) {
				if (getChildCount() > 0) {
					View child = getChildAt(0);
					canScrollLeft = child.getLeft() < mListPadding.left;
				}
			}

			mScrollLeft.setVisibility(canScrollLeft ? View.VISIBLE
					: View.INVISIBLE);
		}

		if (mScrollRight != null) {
			boolean canScrollRight;
			int count = getChildCount();

			// Last item is not visible
			canScrollRight = (mFirstPosition + count) < mItemCount;

			// ... Or bottom of the last element is not visible
			if (!canScrollRight && count > 0) {
				View child = getChildAt(count - 1);
				canScrollRight = child.getRight() > getRight()
						- mListPadding.right;
			}

			mScrollRight.setVisibility(canScrollRight ? View.VISIBLE
					: View.INVISIBLE);
		}
	}

	@Override
	@ViewDebug.ExportedProperty
	public View getSelectedView() {
		if (mItemCount > 0 && mSelectedPosition >= 0) {
			return getChildAt(mSelectedPosition - mFirstPosition);
		} else {
			return null;
		}
	}

	/**
	 * List padding is the maximum of the normal view's padding and the padding
	 * of the selector.
	 *
	 * @see android.view.View#getPaddingTop()
	 * @see #getSelector()
	 *
	 * @return The top list padding.
	 */
	public int getListPaddingTop() {
		return mListPadding.top;
	}

	/**
	 * List padding is the maximum of the normal view's padding and the padding
	 * of the selector.
	 *
	 * @see android.view.View#getPaddingBottom()
	 * @see #getSelector()
	 *
	 * @return The bottom list padding.
	 */
	public int getListPaddingBottom() {
		return mListPadding.bottom;
	}

	/**
	 * List padding is the maximum of the normal view's padding and the padding
	 * of the selector.
	 *
	 * @see android.view.View#getPaddingLeft()
	 * @see #getSelector()
	 *
	 * @return The left list padding.
	 */
	public int getListPaddingLeft() {
		return mListPadding.left;
	}

	/**
	 * List padding is the maximum of the normal view's padding and the padding
	 * of the selector.
	 *
	 * @see android.view.View#getPaddingRight()
	 * @see #getSelector()
	 *
	 * @return The right list padding.
	 */
	public int getListPaddingRight() {
		return mListPadding.right;
	}

	/**
	 * Get a view and have it show the data associated with the specified
	 * position. This is called when we have already discovered that the view is
	 * not available for reuse in the recycle bin. The only choices left are
	 * converting an old view or making a new one.
	 *
	 * @param position
	 *            The position to display
	 * @param isScrap
	 *            Array of at least 1 boolean, the first entry will become true
	 *            if the returned view was taken from the scrap heap, false if
	 *            otherwise.
	 *
	 * @return A view displaying the data associated with the specified position
	 */
	@SuppressLint("NewApi")
	protected View obtainView(int position, boolean[] isScrap) {
		isScrap[0] = false;
		View scrapView;

		scrapView = mRecycler.getTransientStateView(position);
		if (scrapView != null) {
			return scrapView;
		}

		scrapView = mRecycler.getScrapView(position);

		View child;
		if (scrapView != null) {
			child = mAdapter.getView(position, scrapView, this);

			if (android.os.Build.VERSION.SDK_INT >= 16) {
				if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
					child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
				}
			}

			if (child != scrapView) {
				mRecycler.addScrapView(scrapView, position);
				if (mCacheColorHint != 0) {
					child.setDrawingCacheBackgroundColor(mCacheColorHint);
				}
			} else {
				isScrap[0] = true;
				child.onFinishTemporaryDetach();
			}
		} else {
			child = mAdapter.getView(position, null, this);

			if (android.os.Build.VERSION.SDK_INT >= 16) {
				if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
					child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
				}
			}

			if (mCacheColorHint != 0) {
				child.setDrawingCacheBackgroundColor(mCacheColorHint);
			}
		}

		if (mAdapterHasStableIds) {
			final ViewGroup.LayoutParams vlp = child.getLayoutParams();
			LayoutParams lp;
			if (vlp == null) {
				lp = (LayoutParams) generateDefaultLayoutParams();
			} else if (!checkLayoutParams(vlp)) {
				lp = (LayoutParams) generateLayoutParams(vlp);
			} else {
				lp = (LayoutParams) vlp;
			}
			lp.itemId = mAdapter.getItemId(position);
			child.setLayoutParams(lp);
		}

		if (mAccessibilityManager.isEnabled()) {
			if (mAccessibilityDelegate == null) {
				mAccessibilityDelegate = new ListItemAccessibilityDelegate();
			}

			// TODO: implement this ( hidden by google )
			// if (child.getAccessibilityDelegate() == null) {
			// child.setAccessibilityDelegate(mAccessibilityDelegate);
			// }
		}

		return child;
	}

	@TargetApi(14)
	class ListItemAccessibilityDelegate extends AccessibilityDelegateCompat {

		@Override
		public void onInitializeAccessibilityNodeInfo(View host,
				AccessibilityNodeInfoCompat info) {
			super.onInitializeAccessibilityNodeInfo(host, info);

			final int position = getPositionForView(host);
			final ListAdapter adapter = getAdapter();

			if ((position == INVALID_POSITION) || (adapter == null)) {
				return;
			}

			if (!isEnabled() || !adapter.isEnabled(position)) {
				return;
			}

			if (position == getSelectedItemPosition()) {
				info.setSelected(true);
				info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION);
			} else {
				info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT);
			}

			if (isClickable()) {
				info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
				info.setClickable(true);
			}

			if (isLongClickable()) {
				info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
				info.setLongClickable(true);
			}

		}

		@Override
		public boolean performAccessibilityAction(View host, int action,
                                                  Bundle arguments) {
			if (super.performAccessibilityAction(host, action, arguments)) {
				return true;
			}

			final int position = getPositionForView(host);
			final ListAdapter adapter = getAdapter();

			if ((position == INVALID_POSITION) || (adapter == null)) {
				// Cannot perform actions on invalid items.
				return false;
			}

			if (!isEnabled() || !adapter.isEnabled(position)) {
				// Cannot perform actions on disabled items.
				return false;
			}

			final long id = getItemIdAtPosition(position);

			switch (action) {
			case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION: {
				if (getSelectedItemPosition() == position) {
					setSelection(INVALID_POSITION);
					return true;
				}
			}
				return false;
			case AccessibilityNodeInfoCompat.ACTION_SELECT: {
				if (getSelectedItemPosition() != position) {
					setSelection(position);
					return true;
				}
			}
				return false;
			case AccessibilityNodeInfoCompat.ACTION_CLICK: {
				if (isClickable()) {
					return performItemClick(host, position, id);
				}
			}
				return false;
			case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK: {
				if (isLongClickable()) {
					return performLongPress(host, position, id);
				}
			}
				return false;
			}

			return false;
		}
	}

	protected void positionSelector(int position, View sel) {
		if (position != INVALID_POSITION) {
			mSelectorPosition = position;
		}

		final Rect selectorRect = mSelectorRect;
		selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(),
				sel.getBottom());
		if (sel instanceof SelectionBoundsAdjuster) {
			((SelectionBoundsAdjuster) sel)
					.adjustListItemSelectionBounds(selectorRect);
		}
		positionSelector(selectorRect.left, selectorRect.top,
				selectorRect.right, selectorRect.bottom);

		final boolean isChildViewEnabled = mIsChildViewEnabled;
		if (sel.isEnabled() != isChildViewEnabled) {
			mIsChildViewEnabled = !isChildViewEnabled;
			if (getSelectedItemPosition() != INVALID_POSITION) {
				refreshDrawableState();
			}
		}
	}

	private void positionSelector(int l, int t, int r, int b) {
		mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding,
				r + mSelectionRightPadding, b + mSelectionBottomPadding);
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		/*
		 * TODO: check this final boolean clipToPadding = (mGroupFlags &
		 * CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) {
		 * saveCount = canvas.save(); final int scrollX = mScrollX; final int
		 * scrollY = mScrollY; canvas.clipRect(scrollX + mPaddingLeft, scrollY +
		 * mPaddingTop, scrollX + mRight - mLeft - mPaddingRight, scrollY +
		 * mBottom - mTop - mPaddingBottom); mGroupFlags &=
		 * ~CLIP_TO_PADDING_MASK; }
		 */

		final boolean drawSelectorOnTop = mDrawSelectorOnTop;
		if (!drawSelectorOnTop) {
			drawSelector(canvas);
		}

		super.dispatchDraw(canvas);

		if (drawSelectorOnTop) {
			drawSelector(canvas);
		}

		/*
		 * TODO: check this if (clipToPadding) {
		 * canvas.restoreToCount(saveCount); mGroupFlags |=
		 * CLIP_TO_PADDING_MASK; }
		 */
	}

	/*
	 * @Override protected boolean isPaddingOffsetRequired() { return (
	 * mGroupFlags & CLIP_TO_PADDING_MASK ) != CLIP_TO_PADDING_MASK; }
	 *
	 * @Override protected int getLeftPaddingOffset() { return ( mGroupFlags &
	 * CLIP_TO_PADDING_MASK ) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft; }
	 *
	 * @Override protected int getTopPaddingOffset() { return ( mGroupFlags &
	 * CLIP_TO_PADDING_MASK ) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop; }
	 *
	 * @Override protected int getRightPaddingOffset() { return ( mGroupFlags &
	 * CLIP_TO_PADDING_MASK ) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight; }
	 *
	 * @Override protected int getBottomPaddingOffset() { return ( mGroupFlags &
	 * CLIP_TO_PADDING_MASK ) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom; }
	 */

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		if (getChildCount() > 0) {
			mDataChanged = true;
			rememberSyncState();
		}
	}

	/**
	 * @return True if the current touch mode requires that we draw the selector
	 *         in the pressed state.
	 */
	boolean touchModeDrawsInPressedState() {
		// FIXME use isPressed for this
		switch (mTouchMode) {
		case TOUCH_MODE_TAP:
		case TOUCH_MODE_DONE_WAITING:
			return true;
		default:
			return false;
		}
	}

	/**
	 * Indicates whether this view is in a state where the selector should be
	 * drawn. This will happen if we have focus but are not in touch mode, or we
	 * are in the middle of displaying the pressed state for an item.
	 *
	 * @return True if the selector should be shown
	 */
	protected boolean shouldShowSelector() {
		return (hasFocus() && !isInTouchMode())
				|| touchModeDrawsInPressedState();
	}

	private void drawSelector(Canvas canvas) {
		if (!mSelectorRect.isEmpty()) {
			final Drawable selector = mSelector;
			selector.setBounds(mSelectorRect);
			selector.draw(canvas);
		}
	}

	/**
	 * Controls whether the selection highlight drawable should be drawn on top
	 * of the item or behind it.
	 *
	 * @param onTop
	 *            If true, the selector will be drawn on the item it is
	 *            highlighting. The default is false.
	 *
	 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
	 */
	public void setDrawSelectorOnTop(boolean onTop) {
		mDrawSelectorOnTop = onTop;
	}

	/**
	 * Set a Drawable that should be used to highlight the currently selected
	 * item.
	 *
	 * @param resID
	 *            A Drawable resource to use as the selection highlight.
	 *
	 * @attr ref android.R.styleable#AbsListView_listSelector
	 */
	public void setSelector(int resID) {
		setSelector(getResources().getDrawable(resID));
	}

	public void setSelector(Drawable sel) {
		if (mSelector != null) {
			mSelector.setCallback(null);
			unscheduleDrawable(mSelector);
		}
		mSelector = sel;
		Rect padding = new Rect();
		sel.getPadding(padding);
		mSelectionLeftPadding = padding.left;
		mSelectionTopPadding = padding.top;
		mSelectionRightPadding = padding.right;
		mSelectionBottomPadding = padding.bottom;
		sel.setCallback(this);
		updateSelectorState();
	}

	/**
	 * Returns the selector {@link android.graphics.drawable.Drawable} that is
	 * used to draw the selection in the list.
	 *
	 * @return the drawable used to display the selector
	 */
	public Drawable getSelector() {
		return mSelector;
	}

	/**
	 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to
	 * see if this is a long press.
	 */
	protected void keyPressed() {
		if (!isEnabled() || !isClickable()) {
			return;
		}

		Drawable selector = mSelector;
		Rect selectorRect = mSelectorRect;
		if (selector != null && (isFocused() || touchModeDrawsInPressedState())
				&& !selectorRect.isEmpty()) {

			final View v = getChildAt(mSelectedPosition - mFirstPosition);

			if (v != null) {
				if (v.hasFocusable())
					return;
				v.setPressed(true);
			}
			setPressed(true);

			final boolean longClickable = isLongClickable();
			Drawable d = selector.getCurrent();
			if (d != null && d instanceof TransitionDrawable) {
				if (longClickable) {
					((TransitionDrawable) d).startTransition(ViewConfiguration
							.getLongPressTimeout());
				} else {
					((TransitionDrawable) d).resetTransition();
				}
			}
			if (longClickable && !mDataChanged) {
				if (mPendingCheckForKeyLongPress == null) {
					mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
				}
				mPendingCheckForKeyLongPress.rememberWindowAttachCount();
				postDelayed(mPendingCheckForKeyLongPress,
						ViewConfiguration.getLongPressTimeout());
			}
		}
	}

	public void setScrollIndicators(View left, View right) {
		mScrollLeft = left;
		mScrollRight = right;
	}

	public static final int[] STATESET_NOTHING = new int[] { 0 };

	void updateSelectorState() {
		if (mSelector != null) {
			if (shouldShowSelector()) {
				mSelector.setState(getDrawableState());
			} else {
				mSelector.setState(STATESET_NOTHING);
			}
		}
	}

	@Override
	protected void drawableStateChanged() {
		super.drawableStateChanged();
		updateSelectorState();
	}

	@SuppressLint("Override")
	@Override
	protected int[] onCreateDrawableState(int extraSpace) {
		// If the child view is enabled then do the default behavior.
		if (mIsChildViewEnabled) {
			// Common case
			return super.onCreateDrawableState(extraSpace);
		}

		// The selector uses this View's drawable state. The selected child view
		// is disabled, so we need to remove the enabled state from the drawable
		// states.
		final int enabledState = ENABLED_STATE_SET[0];

		// If we don't have any extra space, it will return one of the static
		// state arrays,
		// and clearing the enabled state on those arrays is a bad thing! If we
		// specify
		// we need extra space, it will create+copy into a new array that safely
		// mutable.
		int[] state = super.onCreateDrawableState(extraSpace + 1);
		int enabledPos = -1;
		for (int i = state.length - 1; i >= 0; i--) {
			if (state[i] == enabledState) {
				enabledPos = i;
				break;
			}
		}

		// Remove the enabled state
		if (enabledPos >= 0) {
			System.arraycopy(state, enabledPos + 1, state, enabledPos,
					state.length - enabledPos - 1);
		}

		return state;
	}

	@Override
	public boolean verifyDrawable(Drawable dr) {
		return mSelector == dr || super.verifyDrawable(dr);
	}

	@TargetApi(11)
	@Override
	public void jumpDrawablesToCurrentState() {
		super.jumpDrawablesToCurrentState();
		if (mSelector != null)
			mSelector.jumpToCurrentState();
	}

	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();

		final ViewTreeObserver treeObserver = getViewTreeObserver();
		treeObserver.addOnTouchModeChangeListener(this);

		if (mAdapter != null && mDataSetObserver == null) {
			mDataSetObserver = new AdapterDataSetObserver();
			mAdapter.registerDataSetObserver(mDataSetObserver);

			// Data may have changed while we were detached. Refresh.
			mDataChanged = true;
			mOldItemCount = mItemCount;
			mItemCount = mAdapter.getCount();
		}
		mIsAttached = true;
	}

	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();

		// Dismiss the popup in case onSaveInstanceState() was not invoked

		// Detach any view left in the scrap heap
		mRecycler.clear();

		final ViewTreeObserver treeObserver = getViewTreeObserver();
		treeObserver.removeOnTouchModeChangeListener(this);

		if (mAdapter != null && mDataSetObserver != null) {
			mAdapter.unregisterDataSetObserver(mDataSetObserver);
			mDataSetObserver = null;
		}

		if (mFlingRunnable != null) {
			removeCallbacks(mFlingRunnable);
		}

		if (mPositionScroller != null) {
			mPositionScroller.stop();
		}

		if (mClearScrollingCache != null) {
			removeCallbacks(mClearScrollingCache);
		}

		if (mPerformClick != null) {
			removeCallbacks(mPerformClick);
		}

		if (mTouchModeReset != null) {
			removeCallbacks(mTouchModeReset);
			mTouchModeReset = null;
		}
		mIsAttached = false;
	}

	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		super.onWindowFocusChanged(hasWindowFocus);

		final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;

		if (!hasWindowFocus) {
			setChildrenDrawingCacheEnabled(false);
			if (mFlingRunnable != null) {
				removeCallbacks(mFlingRunnable);
				// let the fling runnable report it's new state which
				// should be idle
				mFlingRunnable.endFling();
				if (mPositionScroller != null) {
					mPositionScroller.stop();
				}
				if (getScrollX() != 0) {
					mViewHelper.setScrollX(0);
					finishGlows();
					invalidate();
				}
			}
			// Always hide the type filter

			if (touchMode == TOUCH_MODE_OFF) {
				// Remember the last selected element
				mResurrectToPosition = mSelectedPosition;
			}
		} else {
			// If we changed touch mode since the last time we had focus
			if (touchMode != mLastTouchMode
					&& mLastTouchMode != TOUCH_MODE_UNKNOWN) {
				// If we come back in trackball mode, we bring the selection
				// back
				if (touchMode == TOUCH_MODE_OFF) {
					// This will trigger a layout
					resurrectSelection();

					// If we come back in touch mode, then we want to hide the
					// selector
				} else {
					hideSelector();
					mLayoutMode = LAYOUT_NORMAL;
					layoutChildren();
				}
			}
		}

		mLastTouchMode = touchMode;
	}

	/**
	 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}.
	 * This methods knows the view, position and ID of the item that received
	 * the long press.
	 *
	 * @param view
	 *            The view that received the long press.
	 * @param position
	 *            The position of the item that received the long press.
	 * @param id
	 *            The ID of the item that received the long press.
	 * @return The extra information that should be returned by
	 *         {@link #getContextMenuInfo()}.
	 */
	ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
		return new AdapterContextMenuInfo(view, position, id);
	}

	/**
	 * A base class for Runnables that will check that their view is still
	 * attached to the original window as when the Runnable was created.
	 *
	 */
	private class WindowRunnnable {

		private int mOriginalAttachCount;

		public void rememberWindowAttachCount() {
			mOriginalAttachCount = getWindowAttachCount();
		}

		public boolean sameWindow() {
			return hasWindowFocus()
					&& getWindowAttachCount() == mOriginalAttachCount;
		}
	}

	private class PerformClick extends WindowRunnnable implements Runnable {

		int mClickMotionPosition;

		@Override
		public void run() {
			// The data has changed since we posted this action in the event
			// queue,
			// bail out before bad things happen
			if (mDataChanged)
				return;

			final ListAdapter adapter = mAdapter;
			final int motionPosition = mClickMotionPosition;
			if (adapter != null && mItemCount > 0
					&& motionPosition != INVALID_POSITION
					&& motionPosition < adapter.getCount() && sameWindow()) {
				final View view = getChildAt(motionPosition - mFirstPosition);
				// If there is no view, something bad happened (the view
				// scrolled off the
				// screen, etc.) and we should cancel the click
				if (view != null) {
					performItemClick(view, motionPosition,
							adapter.getItemId(motionPosition));
				}
			}
		}
	}

	private class CheckForLongPress extends WindowRunnnable implements Runnable {

		@Override
		public void run() {
			final int motionPosition = mMotionPosition;
			final View child = getChildAt(motionPosition - mFirstPosition);
			if (child != null) {
				final int longPressPosition = mMotionPosition;
				final long longPressId = mAdapter.getItemId(mMotionPosition);

				boolean handled = false;
				if (sameWindow() && !mDataChanged) {
					handled = performLongPress(child, longPressPosition,
							longPressId);
				}
				if (handled) {
					mTouchMode = TOUCH_MODE_REST;
					setPressed(false);
					child.setPressed(false);
				} else {
					mTouchMode = TOUCH_MODE_DONE_WAITING;
				}
			}
		}
	}

	private class CheckForKeyLongPress extends WindowRunnnable implements
            Runnable {

		@Override
		public void run() {
			if (isPressed() && mSelectedPosition >= 0) {
				int index = mSelectedPosition - mFirstPosition;
				View v = getChildAt(index);

				if (!mDataChanged) {
					boolean handled = false;
					if (sameWindow()) {
						handled = performLongPress(v, mSelectedPosition,
								mSelectedColId);
					}
					if (handled) {
						setPressed(false);
						v.setPressed(false);
					}
				} else {
					setPressed(false);
					if (v != null)
						v.setPressed(false);
				}
			}
		}
	}

	boolean performLongPress(final View child, final int longPressPosition,
                             final long longPressId) {

		// CHOICE_MODE_MULTIPLE_MODAL takes over long press.
		if (android.os.Build.VERSION.SDK_INT >= 11) {
			if (mChoiceMode == AbsListView.CHOICE_MODE_MULTIPLE_MODAL) {
				if (mChoiceActionMode == null
						&& (mChoiceActionMode = startActionMode((MultiChoiceModeWrapper) mMultiChoiceModeCallback)) != null) {
					setItemChecked(longPressPosition, true);
					performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
				}
				return true;
			}
		}

		boolean handled = false;
		if (mOnItemLongClickListener != null) {
			handled = mOnItemLongClickListener.onItemLongClick(
					AbsHListView.this, child, longPressPosition, longPressId);
		}
		if (!handled) {
			mContextMenuInfo = createContextMenuInfo(child, longPressPosition,
					longPressId);
			handled = super.showContextMenuForChild(AbsHListView.this);
		}
		if (handled) {
			performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
		}
		return handled;
	}

	@Override
	protected ContextMenuInfo getContextMenuInfo() {
		return mContextMenuInfo;
	}

	public boolean showContextMenu(float x, float y, int metaState) {
		final int position = pointToPosition((int) x, (int) y);
		if (position != INVALID_POSITION) {
			final long id = mAdapter.getItemId(position);
			View child = getChildAt(position - mFirstPosition);
			if (child != null) {
				mContextMenuInfo = createContextMenuInfo(child, position, id);
				return super.showContextMenuForChild(AbsHListView.this);
			}
		}
		return showContextMenu(x, y, metaState);
	}

	@Override
	public boolean showContextMenuForChild(View originalView) {
		final int longPressPosition = getPositionForView(originalView);
		if (longPressPosition >= 0) {
			final long longPressId = mAdapter.getItemId(longPressPosition);
			boolean handled = false;

			if (mOnItemLongClickListener != null) {
				handled = mOnItemLongClickListener.onItemLongClick(
						AbsHListView.this, originalView, longPressPosition,
						longPressId);
			}
			if (!handled) {
				mContextMenuInfo = createContextMenuInfo(
						getChildAt(longPressPosition - mFirstPosition),
						longPressPosition, longPressId);
				handled = super.showContextMenuForChild(originalView);
			}

			return handled;
		}
		return false;
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		return false;
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		switch (keyCode) {
		case KeyEvent.KEYCODE_DPAD_CENTER:
		case KeyEvent.KEYCODE_ENTER:
			if (!isEnabled()) {
				return true;
			}
			if (isClickable() && isPressed() && mSelectedPosition >= 0
					&& mAdapter != null
					&& mSelectedPosition < mAdapter.getCount()) {

				final View view = getChildAt(mSelectedPosition - mFirstPosition);
				if (view != null) {
					performItemClick(view, mSelectedPosition, mSelectedColId);
					view.setPressed(false);
				}
				setPressed(false);
				return true;
			}
			break;
		}
		return super.onKeyUp(keyCode, event);
	}

	@Override
	protected void dispatchSetPressed(boolean pressed) {
		// Don't dispatch setPressed to our children. We call setPressed on
		// ourselves to
		// get the selector in the right state, but we don't want to press each
		// child.
	}

	/**
	 * Maps a point to a position in the list.
	 *
	 * @param x
	 *            X in local coordinate
	 * @param y
	 *            Y in local coordinate
	 * @return The position of the item which contains the specified point, or
	 *         {@link #INVALID_POSITION} if the point does not intersect an
	 *         item.
	 */
	public int pointToPosition(int x, int y) {
		Rect frame = mTouchFrame;
		if (frame == null) {
			mTouchFrame = new Rect();
			frame = mTouchFrame;
		}

		final int count = getChildCount();
		for (int i = count - 1; i >= 0; i--) {
			final View child = getChildAt(i);
			if (child.getVisibility() == View.VISIBLE) {
				child.getHitRect(frame);
				if (frame.contains(x, y)) {
					return mFirstPosition + i;
				}
			}
		}
		return INVALID_POSITION;
	}

	/**
	 * Maps a point to a the colId of the item which intersects that point.
	 *
	 * @param x
	 *            X in local coordinate
	 * @param y
	 *            Y in local coordinate
	 * @return The colId of the item which contains the specified point, or
	 *         {@link #INVALID_COL_ID} if the point does not intersect an item.
	 */
	public long pointToColId(int x, int y) {
		int position = pointToPosition(x, y);
		if (position >= 0) {
			return mAdapter.getItemId(position);
		}
		return INVALID_COL_ID;
	}

	final class CheckForTap implements Runnable {

		@Override
		public void run() {
			if (mTouchMode == TOUCH_MODE_DOWN) {
				mTouchMode = TOUCH_MODE_TAP;
				final View child = getChildAt(mMotionPosition - mFirstPosition);
				if (child != null && !child.hasFocusable()) {
					mLayoutMode = LAYOUT_NORMAL;

					if (!mDataChanged) {
						child.setPressed(true);
						setPressed(true);
						layoutChildren();
						positionSelector(mMotionPosition, child);
						refreshDrawableState();

						final int longPressTimeout = ViewConfiguration
								.getLongPressTimeout();
						final boolean longClickable = isLongClickable();

						if (mSelector != null) {
							Drawable d = mSelector.getCurrent();
							if (d != null && d instanceof TransitionDrawable) {
								if (longClickable) {
									((TransitionDrawable) d)
											.startTransition(longPressTimeout);
								} else {
									((TransitionDrawable) d).resetTransition();
								}
							}
						}

						if (longClickable) {
							if (mPendingCheckForLongPress == null) {
								mPendingCheckForLongPress = new CheckForLongPress();
							}
							mPendingCheckForLongPress
									.rememberWindowAttachCount();
							postDelayed(mPendingCheckForLongPress,
									longPressTimeout);
						} else {
							mTouchMode = TOUCH_MODE_DONE_WAITING;
						}
					} else {
						mTouchMode = TOUCH_MODE_DONE_WAITING;
					}
				}
			}
		}
	}

	private boolean startScrollIfNeeded(int x) {
		// Check if we have moved far enough that it looks more like a
		// scroll than a tap
		final int deltaX = x - mMotionX;
		final int distance = Math.abs(deltaX);
		final boolean overscroll = getScrollX() != 0;
		if (overscroll || distance > mTouchSlop) {
			createScrollingCache();
			if (overscroll) {
				mTouchMode = TOUCH_MODE_OVERSCROLL;
				mMotionCorrection = 0;
			} else {
				mTouchMode = TOUCH_MODE_SCROLL;
				mMotionCorrection = deltaX > 0 ? mTouchSlop : -mTouchSlop;
			}
			final Handler handler = getHandler();
			// Handler should not be null unless the AbsListView is not attached
			// to a
			// window, which would make it very hard to scroll it... but the
			// monkeys
			// say it's possible.
			if (handler != null) {
				handler.removeCallbacks(mPendingCheckForLongPress);
			}
			setPressed(false);
			View motionView = getChildAt(mMotionPosition - mFirstPosition);
			if (motionView != null) {
				motionView.setPressed(false);
			}
			reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
			// Time to start stealing events! Once we've stolen them, don't let
			// anyone
			// steal from us
			final ViewParent parent = getParent();
			if (parent != null) {
				parent.requestDisallowInterceptTouchEvent(true);
			}
			scrollIfNeeded(x);
			return true;
		}

		return false;
	}

	private void scrollIfNeeded(int x) {
		final int rawDeltaX = x - mMotionX;
		final int deltaX = rawDeltaX - mMotionCorrection;
		int incrementalDeltaX = mLastX != Integer.MIN_VALUE ? x - mLastX
				: deltaX;

		if (mTouchMode == TOUCH_MODE_SCROLL) {

			if (x != mLastX) {
				// We may be here after stopping a fling and continuing to
				// scroll.
				// If so, we haven't disallowed intercepting touch events yet.
				// Make sure that we do so in case we're in a parent that can
				// intercept.
				if (Math.abs(rawDeltaX) > mTouchSlop) {
					final ViewParent parent = getParent();
					if (parent != null) {
						parent.requestDisallowInterceptTouchEvent(true);
					}
				}

				final int motionIndex;
				if (mMotionPosition >= 0) {
					motionIndex = mMotionPosition - mFirstPosition;
				} else {
					// If we don't have a motion position that we can reliably
					// track,
					// pick something in the middle to make a best guess at
					// things below.
					motionIndex = getChildCount() / 2;
				}

				int motionViewPrevLeft = 0;
				View motionView = this.getChildAt(motionIndex);
				if (motionView != null) {
					motionViewPrevLeft = motionView.getLeft();
				}

				// No need to do all this work if we're not going to move anyway
				boolean atEdge = false;
				if (incrementalDeltaX != 0) {
					atEdge = trackMotionScroll(deltaX, incrementalDeltaX);
				}

				// Check to see if we have bumped into the scroll limit
				motionView = this.getChildAt(motionIndex);
				if (motionView != null) {
					// Check if the top of the motion view is where it is
					// supposed to be
					final int motionViewRealLeft = motionView.getLeft();
					if (atEdge) {
						// Apply overscroll

						int overscroll = -incrementalDeltaX
								- (motionViewRealLeft - motionViewPrevLeft);
						overScrollBy(overscroll, 0, getScrollX(), 0, 0, 0,
								mOverscrollDistance, 0, true);
						if (Math.abs(mOverscrollDistance) == Math
								.abs(getScrollX())) {
							// Don't allow overfling if we're at the edge.
							if (mVelocityTracker != null) {
								mVelocityTracker.clear();
							}
						}

						final int overscrollMode = getOverScrollMode();

						if (overscrollMode == OVER_SCROLL_ALWAYS
								|| (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
							mDirection = 0; // Reset when entering overscroll.
							mTouchMode = TOUCH_MODE_OVERSCROLL;
							if (rawDeltaX > 0) {
								mEdgeGlowTop.onPull((float) overscroll
										/ getWidth());
								if (!mEdgeGlowBottom.isFinished()) {
									mEdgeGlowBottom.onRelease();
								}
								invalidate();
							} else if (rawDeltaX < 0) {
								mEdgeGlowBottom.onPull((float) overscroll
										/ getWidth());
								if (!mEdgeGlowTop.isFinished()) {
									mEdgeGlowTop.onRelease();
								}
								invalidate();
							}
						}
					}
					mMotionX = x;
				}
				mLastX = x;
			}
		} else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
			if (x != mLastX) {
				final int oldScroll = getScrollX();
				final int newScroll = oldScroll - incrementalDeltaX;
				int newDirection = x > mLastX ? 1 : -1;

				if (mDirection == 0) {
					mDirection = newDirection;
				}

				int overScrollDistance = -incrementalDeltaX;
				if ((newScroll < 0 && oldScroll >= 0)
						|| (newScroll > 0 && oldScroll <= 0)) {
					overScrollDistance = -oldScroll;
					incrementalDeltaX += overScrollDistance;
				} else {
					incrementalDeltaX = 0;
				}

				if (overScrollDistance != 0) {
					overScrollBy(overScrollDistance, 0, getScrollX(), 0, 0, 0,
							mOverscrollDistance, 0, true);
					final int overscrollMode = getOverScrollMode();
					if (overscrollMode == OVER_SCROLL_ALWAYS
							|| (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
						if (rawDeltaX > 0) {
							mEdgeGlowTop.onPull((float) overScrollDistance
									/ getWidth());
							if (!mEdgeGlowBottom.isFinished()) {
								mEdgeGlowBottom.onRelease();
							}
							invalidate();
						} else if (rawDeltaX < 0) {
							mEdgeGlowBottom.onPull((float) overScrollDistance
									/ getWidth());
							if (!mEdgeGlowTop.isFinished()) {
								mEdgeGlowTop.onRelease();
							}
							invalidate();
						}
					}
				}

				if (incrementalDeltaX != 0) {
					// Coming back to 'real' list scrolling
					if (getScrollX() != 0) {
						mViewHelper.setScrollX(0);
						invalidateParentIfNeeded();
					}

					trackMotionScroll(incrementalDeltaX, incrementalDeltaX);

					mTouchMode = TOUCH_MODE_SCROLL;

					// We did not scroll the full amount. Treat this essentially
					// like the
					// start of a new touch scroll
					final int motionPosition = findClosestMotionCol(x);

					mMotionCorrection = 0;
					View motionView = getChildAt(motionPosition
							- mFirstPosition);
					mMotionViewOriginalLeft = motionView != null ? motionView
							.getLeft() : 0;
					mMotionX = x;
					mMotionPosition = motionPosition;
				}
				mLastX = x;
				mDirection = newDirection;
			}
		}
	}

	@TargetApi(11)
	protected void invalidateParentIfNeeded() {
		if (mViewHelper.isHardwareAccelerated() && getParent() instanceof View) {
			((View) getParent()).invalidate();
		}
	}

	@Override
	public void onTouchModeChanged(boolean isInTouchMode) {
		if (isInTouchMode) {
			// Get rid of the selection when we enter touch mode
			hideSelector();
			// Layout, but only if we already have done so previously.
			// (Otherwise may clobber a LAYOUT_SYNC layout that was requested to
			// restore
			// state.)
			if (getWidth() > 0 && getChildCount() > 0) {
				// We do not lose focus initiating a touch (since AbsListView is
				// focusable in
				// touch mode). Force an initial layout to get rid of the
				// selection.
				layoutChildren();
			}
			updateSelectorState();
		} else {
			int touchMode = mTouchMode;
			if (touchMode == TOUCH_MODE_OVERSCROLL
					|| touchMode == TOUCH_MODE_OVERFLING) {
				if (mFlingRunnable != null) {
					mFlingRunnable.endFling();
				}
				if (mPositionScroller != null) {
					mPositionScroller.stop();
				}

				if (getScrollX() != 0) {
					mViewHelper.setScrollX(0);
					finishGlows();
					invalidate();
				}
			}
		}
	}

	/**
	 * Performs button-related actions during a touch down event.
	 *
	 * @param event
	 *            The event.
	 * @return True if the down was consumed.
	 *
	 */
	@TargetApi(14)
	protected boolean performButtonActionOnTouchDown(MotionEvent event) {
		if (android.os.Build.VERSION.SDK_INT >= 14) {
			if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
				if (showContextMenu(event.getX(), event.getY(),
						event.getMetaState())) {
					return true;
				}
			}
		}
		return false;
	}

	@SuppressLint("Override")
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (!isEnabled()) {
			// A disabled view that is clickable still consumes the touch
			// events, it just doesn't respond to them.
			return isClickable() || isLongClickable();
		}

		if (mPositionScroller != null) {
			mPositionScroller.stop();
		}

		if (!mIsAttached) {
			// Something isn't right.
			// Since we rely on being attached to get data set change
			// notifications,
			// don't risk doing anything where we might try to resync and find
			// things
			// in a bogus state.
			return false;
		}

		final int action = ev.getAction();

		View v;

		initVelocityTrackerIfNotExists();
		mVelocityTracker.addMovement(ev);

		switch (action & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN: {
			switch (mTouchMode) {
			case TOUCH_MODE_OVERFLING: {
				mFlingRunnable.endFling();
				if (mPositionScroller != null) {
					mPositionScroller.stop();
				}
				mTouchMode = TOUCH_MODE_OVERSCROLL;
				mMotionY = (int) ev.getY();
				mMotionX = mLastX = (int) ev.getX();
				mMotionCorrection = 0;
				mActivePointerId = ev.getPointerId(0);
				mDirection = 0;
				break;
			}

			default: {
				mActivePointerId = ev.getPointerId(0);
				final int x = (int) ev.getX();
				final int y = (int) ev.getY();
				int motionPosition = pointToPosition(x, y);
				if (!mDataChanged) {
					if ((mTouchMode != TOUCH_MODE_FLING)
							&& (motionPosition >= 0)
							&& (getAdapter().isEnabled(motionPosition))) {
						// User clicked on an actual view (and was not stopping
						// a fling).
						// It might be a click or a scroll. Assume it is a click
						// until
						// proven otherwise
						mTouchMode = TOUCH_MODE_DOWN;
						// FIXME Debounce
						if (mPendingCheckForTap == null) {
							mPendingCheckForTap = new CheckForTap();
						}
						postDelayed(mPendingCheckForTap,
								ViewConfiguration.getTapTimeout());
					} else {
						if (mTouchMode == TOUCH_MODE_FLING) {
							// Stopped a fling. It is a scroll.
							createScrollingCache();
							mTouchMode = TOUCH_MODE_SCROLL;
							mMotionCorrection = 0;
							motionPosition = findMotionCol(x);
							mFlingRunnable.flywheelTouch();
						}
					}
				}

				if (motionPosition >= 0) {
					// Remember where the motion event started
					v = getChildAt(motionPosition - mFirstPosition);
					mMotionViewOriginalLeft = v.getLeft();
				}
				mMotionX = x;
				mMotionY = y;
				mMotionPosition = motionPosition;
				mLastX = Integer.MIN_VALUE;
				break;
			}
			}

			if (performButtonActionOnTouchDown(ev)) {
				if (mTouchMode == TOUCH_MODE_DOWN) {
					removeCallbacks(mPendingCheckForTap);
				}
			}
			break;
		}

		case MotionEvent.ACTION_MOVE: {
			int pointerIndex = ev.findPointerIndex(mActivePointerId);
			if (pointerIndex == -1) {
				pointerIndex = 0;
				mActivePointerId = ev.getPointerId(pointerIndex);
			}
			final int x = (int) ev.getX(pointerIndex);

			if (mDataChanged) {
				// Re-sync everything if data has been changed
				// since the scroll operation can query the adapter.
				layoutChildren();
			}

			switch (mTouchMode) {
			case TOUCH_MODE_DOWN:
			case TOUCH_MODE_TAP:
			case TOUCH_MODE_DONE_WAITING:
				// Check if we have moved far enough that it looks more like a
				// scroll than a tap
				startScrollIfNeeded(x);
				break;
			case TOUCH_MODE_SCROLL:
			case TOUCH_MODE_OVERSCROLL:
				scrollIfNeeded(x);
				break;
			}
			break;
		}

		case MotionEvent.ACTION_UP: {
			switch (mTouchMode) {
			case TOUCH_MODE_DOWN:
			case TOUCH_MODE_TAP:
			case TOUCH_MODE_DONE_WAITING:
				final int motionPosition = mMotionPosition;
				final View child = getChildAt(motionPosition - mFirstPosition);

				final float x = ev.getX();
				final boolean inList = x > mListPadding.left
						&& x < getWidth() - mListPadding.right;

				if (child != null && !child.hasFocusable() && inList) {
					if (mTouchMode != TOUCH_MODE_DOWN) {
						child.setPressed(false);
					}

					if (mPerformClick == null) {
						mPerformClick = new PerformClick();
					}

					final AbsHListView.PerformClick performClick = mPerformClick;
					performClick.mClickMotionPosition = motionPosition;
					performClick.rememberWindowAttachCount();

					mResurrectToPosition = motionPosition;

					if (mTouchMode == TOUCH_MODE_DOWN
							|| mTouchMode == TOUCH_MODE_TAP) {
						final Handler handler = getHandler();
						if (handler != null) {
							handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap
									: mPendingCheckForLongPress);
						}
						mLayoutMode = LAYOUT_NORMAL;

						if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
							mTouchMode = TOUCH_MODE_TAP;
							setSelectedPositionInt(mMotionPosition);
							layoutChildren();
							child.setPressed(true);
							positionSelector(mMotionPosition, child);
							setPressed(true);
							if (mSelector != null) {
								Drawable d = mSelector.getCurrent();
								if (d != null
										&& d instanceof TransitionDrawable) {
									((TransitionDrawable) d).resetTransition();
								}
							}
							if (mTouchModeReset != null) {
								removeCallbacks(mTouchModeReset);
							}
							mTouchModeReset = new Runnable() {

								@Override
								public void run() {
									mTouchMode = TOUCH_MODE_REST;
									child.setPressed(false);
									setPressed(false);
									if (!mDataChanged) {
										performClick.run();
									}
								}
							};
							postDelayed(mTouchModeReset,
									ViewConfiguration.getPressedStateDuration());
						} else {
							mTouchMode = TOUCH_MODE_REST;
							updateSelectorState();
						}
						return true;
					} else if (!mDataChanged
							&& mAdapter.isEnabled(motionPosition)) {
						performClick.run();
					}
				}
				mTouchMode = TOUCH_MODE_REST;
				updateSelectorState();
				break;
			case TOUCH_MODE_SCROLL:
				final int childCount = getChildCount();
				if (childCount > 0) {
					final int firstChildLeft = getChildAt(0).getLeft();
					final int lastChildRight = getChildAt(childCount - 1)
							.getRight();
					final int contentLeft = mListPadding.left;
					final int contentRight = getWidth() - mListPadding.right;
					if (mFirstPosition == 0 && firstChildLeft >= contentLeft
							&& mFirstPosition + childCount < mItemCount
							&& lastChildRight <= getWidth() - contentRight) {
						mTouchMode = TOUCH_MODE_REST;
						reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
					} else {
						final VelocityTracker velocityTracker = mVelocityTracker;
						velocityTracker.computeCurrentVelocity(1000,
								mMaximumVelocity);

						final int initialVelocity = (int) (velocityTracker
								.getXVelocity(mActivePointerId) * mVelocityScale);
						// Fling if we have enough velocity and we aren't at a
						// boundary.
						// Since we can potentially overfling more than we can
						// overscroll, don't
						// allow the weird behavior where you can scroll to a
						// boundary then
						// fling further.
						if (Math.abs(initialVelocity) > mMinimumVelocity
								&& !((mFirstPosition == 0 && firstChildLeft == contentLeft
										- mOverscrollDistance) || (mFirstPosition
										+ childCount == mItemCount && lastChildRight == contentRight
										+ mOverscrollDistance))) {
							if (mFlingRunnable == null) {
								mFlingRunnable = new FlingRunnable();
							}
							reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);

							mFlingRunnable.start(-initialVelocity);
						} else {
							mTouchMode = TOUCH_MODE_REST;
							reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
							if (mFlingRunnable != null) {
								mFlingRunnable.endFling();
							}
							if (mPositionScroller != null) {
								mPositionScroller.stop();
							}
						}
					}
				} else {
					mTouchMode = TOUCH_MODE_REST;
					reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
				}
				break;

			case TOUCH_MODE_OVERSCROLL:
				if (mFlingRunnable == null) {
					mFlingRunnable = new FlingRunnable();
				}
				final VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
				final int initialVelocity = (int) velocityTracker
						.getXVelocity(mActivePointerId);

				reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
				if (Math.abs(initialVelocity) > mMinimumVelocity) {
					mFlingRunnable.startOverfling(-initialVelocity);
				} else {
					mFlingRunnable.startSpringback();
				}

				break;
			}

			setPressed(false);

			if (mEdgeGlowTop != null) {
				mEdgeGlowTop.onRelease();
				mEdgeGlowBottom.onRelease();
			}

			// Need to redraw since we probably aren't drawing the selector
			// anymore
			invalidate();

			final Handler handler = getHandler();
			if (handler != null) {
				handler.removeCallbacks(mPendingCheckForLongPress);
			}

			recycleVelocityTracker();

			mActivePointerId = INVALID_POINTER;
			break;
		}

		case MotionEvent.ACTION_CANCEL: {
			switch (mTouchMode) {
			case TOUCH_MODE_OVERSCROLL:
				if (mFlingRunnable == null) {
					mFlingRunnable = new FlingRunnable();
				}
				mFlingRunnable.startSpringback();
				break;

			case TOUCH_MODE_OVERFLING:
				// Do nothing - let it play out.
				break;

			default:
				mTouchMode = TOUCH_MODE_REST;
				setPressed(false);
				View motionView = this.getChildAt(mMotionPosition
						- mFirstPosition);
				if (motionView != null) {
					motionView.setPressed(false);
				}
				clearScrollingCache();

				final Handler handler = getHandler();
				if (handler != null) {
					handler.removeCallbacks(mPendingCheckForLongPress);
				}

				recycleVelocityTracker();
			}

			if (mEdgeGlowTop != null) {
				mEdgeGlowTop.onRelease();
				mEdgeGlowBottom.onRelease();
			}
			mActivePointerId = INVALID_POINTER;
			break;
		}

		case MotionEvent.ACTION_POINTER_UP: {
			onSecondaryPointerUp(ev);
			final int x = mMotionX;
			final int y = mMotionY;
			final int motionPosition = pointToPosition(x, y);
			if (motionPosition >= 0) {
				// Remember where the motion event started
				v = getChildAt(motionPosition - mFirstPosition);
				mMotionViewOriginalLeft = v.getLeft();
				mMotionPosition = motionPosition;
			}
			mLastX = x;
			break;
		}

		case MotionEvent.ACTION_POINTER_DOWN: {
			// New pointers take over dragging duties
			final int index = ev.getActionIndex();
			final int id = ev.getPointerId(index);
			final int x = (int) ev.getX(index);
			final int y = (int) ev.getY(index);
			mMotionCorrection = 0;
			mActivePointerId = id;
			mMotionX = x;
			mMotionY = y;
			final int motionPosition = pointToPosition(x, y);
			if (motionPosition >= 0) {
				// Remember where the motion event started
				v = getChildAt(motionPosition - mFirstPosition);
				mMotionViewOriginalLeft = v.getLeft();
				mMotionPosition = motionPosition;
			}
			mLastX = x;
			break;
		}
		}

		return true;
	}

	@Override
	protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
			boolean clampedY) {
		if (getScrollX() != scrollX) {
			onScrollChanged(scrollX, getScrollY(), getScrollX(), getScrollY());
			mViewHelper.setScrollX(scrollX);
			invalidateParentIfNeeded();

			awakenScrollBars();
		}
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
	@Override
	public boolean onGenericMotionEvent(MotionEvent event) {
		if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
			switch (event.getAction()) {
			case MotionEvent.ACTION_SCROLL: {
				if (mTouchMode == TOUCH_MODE_REST) {
					final float hscroll = event
							.getAxisValue(MotionEvent.AXIS_HSCROLL);
					if (hscroll != 0) {
						final int delta = (int) (hscroll * getHorizontalScrollFactor());
						if (!trackMotionScroll(delta, delta)) {
							return true;
						}
					}
				}
			}
			}
		}
		return super.onGenericMotionEvent(event);
	}

	private float mHorizontalScrollFactor;

	/**
	 * Gets a scale factor that determines the distance the view should scroll
	 * vertically in response to {@link MotionEvent#ACTION_SCROLL}.
	 *
	 * @return The vertical scroll scale factor.
	 */
	protected float getHorizontalScrollFactor() {
		if (mHorizontalScrollFactor == 0) {

			TypedValue outValue = new TypedValue();
			boolean success = getContext().getTheme().resolveAttribute(
					R.attr.hlv_listPreferredItemWidth, outValue, true);

			if (success) {
				mHorizontalScrollFactor = outValue.getDimension(getContext()
						.getResources().getDisplayMetrics());
			} else {
				throw new IllegalStateException(
						"Expected theme to define hlv_listPreferredItemWidth.");
			}
		}
		return mHorizontalScrollFactor;
	}

	/**
	 * TODO: to be implemented
	 */
	@Override
	public void draw(Canvas canvas) {
		super.draw(canvas);
		if (mEdgeGlowTop != null) {
			final int scrollX = getScrollX();
			if (!mEdgeGlowTop.isFinished()) {
				final int restoreCount = canvas.save();
				final int topPadding = mListPadding.top + mGlowPaddingTop;
				final int bottomPadding = mListPadding.bottom
						+ mGlowPaddingBottom;
				final int height = getHeight() - topPadding - bottomPadding;
				// final int width = getWidth();

				int edgeX = Math.min(0, scrollX + mFirstPositionDistanceGuess);
				// canvas.translate(edgeX, topPadding);
				// mEdgeGlowTop.setSize(height, height);

				canvas.rotate(-90);
				canvas.translate(-getHeight() + topPadding, edgeX);
				mEdgeGlowTop.setSize(height, height);

				if (mEdgeGlowTop.draw(canvas)) {
					// mEdgeGlowTop.setPosition( edgeX, topPadding );
					invalidate();
				}
				canvas.restoreToCount(restoreCount);
			}
			if (!mEdgeGlowBottom.isFinished()) {
				final int restoreCount = canvas.save();
				final int topPadding = mListPadding.left + mGlowPaddingTop;
				final int bottomPadding = mListPadding.right
						+ mGlowPaddingBottom;
				final int height = getHeight() - topPadding - bottomPadding;
				final int width = getWidth();

				int edgeX = Math.max(width, scrollX
						+ mLastPositionDistanceGuess);
				canvas.rotate(90);
				canvas.translate(-topPadding, -edgeX);

				mEdgeGlowBottom.setSize(height, height);

				if (mEdgeGlowBottom.draw(canvas)) {
					// Account for the rotation
					// mEdgeGlowBottom.setPosition( edgeX + width, edgeY );
					invalidate();
				}
				canvas.restoreToCount(restoreCount);
			}
		}
	}

	public void setOverScrollEffectPadding(int topPadding, int bottomPadding) {
		mGlowPaddingTop = topPadding;
		mGlowPaddingBottom = bottomPadding;
	}

	private void initOrResetVelocityTracker() {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		} else {
			mVelocityTracker.clear();
		}
	}

	private void initVelocityTrackerIfNotExists() {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
	}

	private void recycleVelocityTracker() {
		if (mVelocityTracker != null) {
			mVelocityTracker.recycle();
			mVelocityTracker = null;
		}
	}

	@Override
	public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
		if (disallowIntercept) {
			recycleVelocityTracker();
		}
		super.requestDisallowInterceptTouchEvent(disallowIntercept);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		int action = ev.getAction();
		View v;

		if (mPositionScroller != null) {
			mPositionScroller.stop();
		}

		if (!mIsAttached) {
			// Something isn't right.
			// Since we rely on being attached to get data set change
			// notifications,
			// don't risk doing anything where we might try to resync and find
			// things
			// in a bogus state.
			return false;
		}

		switch (action & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN: {
			int touchMode = mTouchMode;
			if (touchMode == TOUCH_MODE_OVERFLING
					|| touchMode == TOUCH_MODE_OVERSCROLL) {
				mMotionCorrection = 0;
				return true;
			}

			final int x = (int) ev.getX();
			final int y = (int) ev.getY();
			mActivePointerId = ev.getPointerId(0);

			int motionPosition = findMotionCol(x);
			if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
				// User clicked on an actual view (and was not stopping a
				// fling).
				// Remember where the motion event started
				v = getChildAt(motionPosition - mFirstPosition);
				mMotionViewOriginalLeft = v.getLeft();
				mMotionX = x;
				mMotionY = y;
				mMotionPosition = motionPosition;
				mTouchMode = TOUCH_MODE_DOWN;
				clearScrollingCache();
			}
			mLastX = Integer.MIN_VALUE;
			initOrResetVelocityTracker();
			mVelocityTracker.addMovement(ev);
			if (touchMode == TOUCH_MODE_FLING) {
				return true;
			}
			break;
		}

		case MotionEvent.ACTION_MOVE: {
			switch (mTouchMode) {
			case TOUCH_MODE_DOWN:
				int pointerIndex = ev.findPointerIndex(mActivePointerId);
				if (pointerIndex == -1) {
					pointerIndex = 0;
					mActivePointerId = ev.getPointerId(pointerIndex);
				}
				final int x = (int) ev.getX(pointerIndex);
				initVelocityTrackerIfNotExists();
				mVelocityTracker.addMovement(ev);
				if (startScrollIfNeeded(x)) {
					return true;
				}
				break;
			}
			break;
		}

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP: {
			mTouchMode = TOUCH_MODE_REST;
			mActivePointerId = INVALID_POINTER;
			recycleVelocityTracker();
			reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
			break;
		}

		case MotionEvent.ACTION_POINTER_UP: {
			onSecondaryPointerUp(ev);
			break;
		}
		}

		return false;
	}

	private void onSecondaryPointerUp(MotionEvent ev) {
		final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
		final int pointerId = ev.getPointerId(pointerIndex);
		if (pointerId == mActivePointerId) {
			// This was our active pointer going up. Choose a new
			// active pointer and adjust accordingly.
			// TODO: Make this decision more intelligent.
			final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
			mMotionX = (int) ev.getX(newPointerIndex);
			mMotionY = (int) ev.getY(newPointerIndex);
			mMotionCorrection = 0;
			mActivePointerId = ev.getPointerId(newPointerIndex);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addTouchables(ArrayList<View> views) {
		final int count = getChildCount();
		final int firstPosition = mFirstPosition;
		final ListAdapter adapter = mAdapter;

		if (adapter == null) {
			return;
		}

		for (int i = 0; i < count; i++) {
			final View child = getChildAt(i);
			if (adapter.isEnabled(firstPosition + i)) {
				views.add(child);
			}
			child.addTouchables(views);
		}
	}

	/**
	 * Fires an "on scroll state changed" event to the registered
	 * {@link it.sephiroth.android.library.widget.AbsHListView.OnScrollListener}
	 * , if any. The state change is fired only if the specified state is
	 * different from the previously known state.
	 *
	 * @param newState
	 *            The new scroll state.
	 */
	void reportScrollStateChange(int newState) {
		if (newState != mLastScrollState) {
			if (mOnScrollListener != null) {
				mLastScrollState = newState;
				mOnScrollListener.onScrollStateChanged(this, newState);
			}
		}
	}

	/**
	 * Responsible for fling behavior. Use {@link #start(int)} to initiate a
	 * fling. Each frame of the fling is handled in {@link #run()}. A
	 * FlingRunnable will keep re-posting itself until the fling is done.
	 *
	 */
	private class FlingRunnable implements Runnable {

		/**
		 * Tracks the decay of a fling scroll
		 */
		private final OverScroller mScroller;

		/**
		 * Y value reported by mScroller on the previous fling
		 */
		private int mLastFlingX;

		private final Runnable mCheckFlywheel = new Runnable() {

			@Override
			public void run() {
				final int activeId = mActivePointerId;
				final VelocityTracker vt = mVelocityTracker;
				final OverScroller scroller = mScroller;
				if (vt == null || activeId == INVALID_POINTER) {
					return;
				}

				vt.computeCurrentVelocity(1000, mMaximumVelocity);
				final float xvel = -vt.getXVelocity(activeId);

				if (Math.abs(xvel) >= mMinimumVelocity
						&& scroller.isScrollingInDirection(xvel, 0)) {
					// Keep the fling alive a little longer
					postDelayed(this, FLYWHEEL_TIMEOUT);
				} else {
					endFling();
					mTouchMode = TOUCH_MODE_SCROLL;
					reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
				}
			}
		};

		private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds

		FlingRunnable() {
			mScroller = new OverScroller(getContext());
		}

		void start(int initialVelocity) {
			int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
			mLastFlingX = initialX;
			mScroller.setInterpolator(null);
			mScroller.fling(initialX, 0, initialVelocity, 0, 0,
					Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
			mTouchMode = TOUCH_MODE_FLING;
			mViewHelper.postOnAnimation(this);
		}

		void startSpringback() {
			if (mScroller.springBack(getScrollX(), 0, 0, 0, 0, 0)) {
				mTouchMode = TOUCH_MODE_OVERFLING;
				invalidate();
				mViewHelper.postOnAnimation(this);
			} else {
				mTouchMode = TOUCH_MODE_REST;
				reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
			}
		}

		void startOverfling(int initialVelocity) {
			mScroller.setInterpolator(null);
			mScroller.fling(getScrollX(), 0, initialVelocity, 0,
					Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0, getWidth(), 0);
			mTouchMode = TOUCH_MODE_OVERFLING;
			invalidate();
			mViewHelper.postOnAnimation(this);
		}

		void edgeReached(int delta) {
			mScroller.notifyHorizontalEdgeReached(getScrollX(), 0,
					mOverflingDistance);
			final int overscrollMode = getOverScrollMode();
			if (overscrollMode == OVER_SCROLL_ALWAYS
					|| (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
				mTouchMode = TOUCH_MODE_OVERFLING;
				final int vel = (int) mScroller.getCurrVelocity();
				if (delta > 0) {
					mEdgeGlowTop.onAbsorb(vel);
				} else {
					mEdgeGlowBottom.onAbsorb(vel);
				}
			} else {
				mTouchMode = TOUCH_MODE_REST;
				if (mPositionScroller != null) {
					mPositionScroller.stop();
				}
			}
			invalidate();
			mViewHelper.postOnAnimation(this);
		}

		void startScroll(int distance, int duration, boolean linear) {
			int initialX = distance < 0 ? Integer.MAX_VALUE : 0;
			mLastFlingX = initialX;
			mScroller.setInterpolator(linear ? sLinearInterpolator : null);
			mScroller.startScroll(initialX, 0, distance, 0, duration);
			mTouchMode = TOUCH_MODE_FLING;
			mViewHelper.postOnAnimation(this);
		}

		void endFling() {
			mTouchMode = TOUCH_MODE_REST;

			removeCallbacks(this);
			removeCallbacks(mCheckFlywheel);

			reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
			clearScrollingCache();
			mScroller.abortAnimation();
			// ensure edges
			overScrollBy(0, 0, 0, 0, 0, 0, 0, 0, false);

		}

		void flywheelTouch() {
			postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
		}

		@Override
		public void run() {
			switch (mTouchMode) {
			default:
				endFling();
				return;

			case TOUCH_MODE_SCROLL:
				if (mScroller.isFinished()) {
					return;
				}
				// Fall through
			case TOUCH_MODE_FLING: {
				if (mDataChanged) {
					layoutChildren();
				}

				if (mItemCount == 0 || getChildCount() == 0) {
					endFling();
					return;
				}

				final OverScroller scroller = mScroller;
				boolean more = scroller.computeScrollOffset();
				final int x = scroller.getCurrX();

				// Flip sign to convert finger direction to list items direction
				// (e.g. finger moving down means list is moving towards the
				// top)
				int delta = mLastFlingX - x;

				// Pretend that each frame of a fling scroll is a touch scroll
				if (delta > 0) {
					// List is moving towards the top. Use first view as
					// mMotionPosition
					mMotionPosition = mFirstPosition;
					final View firstView = getChildAt(0);
					mMotionViewOriginalLeft = firstView.getLeft();

					// Don't fling more than 1 screen
					delta = Math.min(getWidth() - getPaddingRight()
							- getPaddingLeft() - 1, delta);
				} else {
					// List is moving towards the bottom. Use last view as
					// mMotionPosition
					int offsetToLast = getChildCount() - 1;
					mMotionPosition = mFirstPosition + offsetToLast;

					final View lastView = getChildAt(offsetToLast);
					mMotionViewOriginalLeft = lastView.getLeft();

					// Don't fling more than 1 screen
					delta = Math.max(-(getWidth() - getPaddingRight()
							- getPaddingLeft() - 1), delta);
				}

				// Check to see if we have bumped into the scroll limit
				View motionView = getChildAt(mMotionPosition - mFirstPosition);
				int oldLeft = 0;
				if (motionView != null) {
					oldLeft = motionView.getLeft();
				}

				// Don't stop just because delta is zero (it could have been
				// rounded)
				final boolean atEdge = trackMotionScroll(delta, delta);
				final boolean atEnd = atEdge && (delta != 0);
				if (atEnd) {
					if (motionView != null) {
						// Tweak the scroll for how far we overshot
						int overshoot = -(delta - (motionView.getLeft() - oldLeft));
						overScrollBy(overshoot, 0, getScrollX(), 0, 0, 0,
								mOverflingDistance, 0, false);
					}
					if (more) {
						edgeReached(delta);
					}
					break;
				}

				if (more && !atEnd) {
					if (atEdge)
						invalidate();
					mLastFlingX = x;
					mViewHelper.postOnAnimation(this);
				} else {
					endFling();

				}
				break;
			}

			case TOUCH_MODE_OVERFLING: {
				final OverScroller scroller = mScroller;
				if (scroller.computeScrollOffset()) {
					final int scrollX = getScrollX();
					final int currX = scroller.getCurrX();
					final int deltaX = currX - scrollX;
					if (overScrollBy(deltaX, 0, scrollX, 0, 0, 0,
							mOverflingDistance, 0, false)) {
						final boolean crossRight = scrollX <= 0 && currX > 0;
						final boolean crossLeft = scrollX >= 0 && currX < 0;
						if (crossRight || crossLeft) {
							int velocity = (int) scroller.getCurrVelocity();
							if (crossLeft)
								velocity = -velocity;

							// Don't flywheel from this; we're just continuing
							// things.
							scroller.abortAnimation();
							start(velocity);
						} else {
							startSpringback();
						}
					} else {
						invalidate();
						mViewHelper.postOnAnimation(this);
					}
				} else {
					endFling();
				}
				break;
			}
			}
		}
	}

	public class PositionScroller implements Runnable {

		private static final int SCROLL_DURATION = 200;

		private static final int MOVE_DOWN_POS = 1;
		private static final int MOVE_UP_POS = 2;
		private static final int MOVE_DOWN_BOUND = 3;
		private static final int MOVE_UP_BOUND = 4;
		private static final int MOVE_OFFSET = 5;

		private int mMode;
		private int mTargetPos;
		private int mBoundPos;
		private int mLastSeenPos;
		private int mScrollDuration;
		private final int mExtraScroll;

		private int mOffsetFromLeft;

		PositionScroller() {
			mExtraScroll = ViewConfiguration.get(getContext())
					.getScaledFadingEdgeLength();
		}

		void start(final int position) {
			stop();

			if (mDataChanged) {
				// Wait until we're back in a stable state to try this.
				mPositionScrollAfterLayout = new Runnable() {

					@Override
					public void run() {
						start(position);
					}
				};
				return;
			}

			final int childCount = getChildCount();
			if (childCount == 0) {
				// Can't scroll without children.
				return;
			}

			final int firstPos = mFirstPosition;
			final int lastPos = firstPos + childCount - 1;

			int viewTravelCount;
			int clampedPosition = Math.max(0,
					Math.min(getCount() - 1, position));
			if (clampedPosition < firstPos) {
				viewTravelCount = firstPos - clampedPosition + 1;
				mMode = MOVE_UP_POS;
			} else if (clampedPosition > lastPos) {
				viewTravelCount = clampedPosition - lastPos + 1;
				mMode = MOVE_DOWN_POS;
			} else {
				scrollToVisible(clampedPosition, INVALID_POSITION,
						SCROLL_DURATION);
				return;
			}

			if (viewTravelCount > 0) {
				mScrollDuration = SCROLL_DURATION / viewTravelCount;
			} else {
				mScrollDuration = SCROLL_DURATION;
			}
			mTargetPos = clampedPosition;
			mBoundPos = INVALID_POSITION;
			mLastSeenPos = INVALID_POSITION;

			mViewHelper.postOnAnimation(this);
		}

		void start(final int position, final int boundPosition) {
			stop();

			if (boundPosition == INVALID_POSITION) {
				start(position);
				return;
			}

			if (mDataChanged) {
				// Wait until we're back in a stable state to try this.
				mPositionScrollAfterLayout = new Runnable() {

					@Override
					public void run() {
						start(position, boundPosition);
					}
				};
				return;
			}

			final int childCount = getChildCount();
			if (childCount == 0) {
				// Can't scroll without children.
				return;
			}

			final int firstPos = mFirstPosition;
			final int lastPos = firstPos + childCount - 1;

			int viewTravelCount;
			int clampedPosition = Math.max(0,
					Math.min(getCount() - 1, position));
			if (clampedPosition < firstPos) {
				final int boundPosFromLast = lastPos - boundPosition;
				if (boundPosFromLast < 1) {
					// Moving would shift our bound position off the screen.
					// Abort.
					return;
				}

				final int posTravel = firstPos - clampedPosition + 1;
				final int boundTravel = boundPosFromLast - 1;
				if (boundTravel < posTravel) {
					viewTravelCount = boundTravel;
					mMode = MOVE_UP_BOUND;
				} else {
					viewTravelCount = posTravel;
					mMode = MOVE_UP_POS;
				}
			} else if (clampedPosition > lastPos) {
				final int boundPosFromFirst = boundPosition - firstPos;
				if (boundPosFromFirst < 1) {
					// Moving would shift our bound position off the screen.
					// Abort.
					return;
				}

				final int posTravel = clampedPosition - lastPos + 1;
				final int boundTravel = boundPosFromFirst - 1;
				if (boundTravel < posTravel) {
					viewTravelCount = boundTravel;
					mMode = MOVE_DOWN_BOUND;
				} else {
					viewTravelCount = posTravel;
					mMode = MOVE_DOWN_POS;
				}
			} else {
				scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
				return;
			}

			if (viewTravelCount > 0) {
				mScrollDuration = SCROLL_DURATION / viewTravelCount;
			} else {
				mScrollDuration = SCROLL_DURATION;
			}
			mTargetPos = clampedPosition;
			mBoundPos = boundPosition;
			mLastSeenPos = INVALID_POSITION;

			mViewHelper.postOnAnimation(this);
		}

		void startWithOffset(int position, int offset) {
			startWithOffset(position, offset, SCROLL_DURATION);
		}

		void startWithOffset(final int position, int offset, final int duration) {
			stop();

			if (mDataChanged) {
				// Wait until we're back in a stable state to try this.
				final int postOffset = offset;
				mPositionScrollAfterLayout = new Runnable() {

					@Override
					public void run() {
						startWithOffset(position, postOffset, duration);
					}
				};
				return;
			}

			final int childCount = getChildCount();
			if (childCount == 0) {
				// Can't scroll without children.
				return;
			}

			offset += getPaddingLeft();

			mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
			mOffsetFromLeft = offset;
			mBoundPos = INVALID_POSITION;
			mLastSeenPos = INVALID_POSITION;
			mMode = MOVE_OFFSET;

			final int firstPos = mFirstPosition;
			final int lastPos = firstPos + childCount - 1;

			int viewTravelCount;
			if (mTargetPos < firstPos) {
				viewTravelCount = firstPos - mTargetPos;
			} else if (mTargetPos > lastPos) {
				viewTravelCount = mTargetPos - lastPos;
			} else {
				// On-screen, just scroll.
				final int targetLeft = getChildAt(mTargetPos - firstPos)
						.getLeft();
				smoothScrollBy(targetLeft - offset, duration, false);
				return;
			}

			// Estimate how many screens we should travel
			final float screenTravelCount = (float) viewTravelCount
					/ childCount;
			mScrollDuration = screenTravelCount < 1 ? duration
					: (int) (duration / screenTravelCount);
			mLastSeenPos = INVALID_POSITION;

			mViewHelper.postOnAnimation(this);
		}

		/**
		 * Scroll such that targetPos is in the visible padded region without
		 * scrolling boundPos out of view. Assumes targetPos is onscreen.
		 */
		void scrollToVisible(int targetPos, int boundPos, int duration) {
			final int firstPos = mFirstPosition;
			final int childCount = getChildCount();
			final int lastPos = firstPos + childCount - 1;
			final int paddedLeft = mListPadding.left;
			final int paddedRight = getWidth() - mListPadding.right;

			if (targetPos < firstPos || targetPos > lastPos) {
				Log.w(TAG, "scrollToVisible called with targetPos " + targetPos
						+ " not visible [" + firstPos + ", " + lastPos + "]");
			}
			if (boundPos < firstPos || boundPos > lastPos) {
				// boundPos doesn't matter, it's already offscreen.
				boundPos = INVALID_POSITION;
			}

			final View targetChild = getChildAt(targetPos - firstPos);
			final int targetLeft = targetChild.getLeft();
			final int targetRight = targetChild.getRight();
			int scrollBy = 0;

			if (targetRight > paddedRight) {
				scrollBy = targetRight - paddedRight;
			}
			if (targetLeft < paddedLeft) {
				scrollBy = targetLeft - paddedLeft;
			}

			if (scrollBy == 0) {
				return;
			}

			if (boundPos >= 0) {
				final View boundChild = getChildAt(boundPos - firstPos);
				final int boundLeft = boundChild.getLeft();
				final int boundRight = boundChild.getRight();
				final int absScroll = Math.abs(scrollBy);

				if (scrollBy < 0 && boundRight + absScroll > paddedRight) {
					// Don't scroll the bound view off the bottom of the screen.
					scrollBy = Math.max(0, boundRight - paddedRight);
				} else if (scrollBy > 0 && boundLeft - absScroll < paddedLeft) {
					// Don't scroll the bound view off the top of the screen.
					scrollBy = Math.min(0, boundLeft - paddedLeft);
				}
			}

			smoothScrollBy(scrollBy, duration);
		}

		public void stop() {
			removeCallbacks(this);
		}

		@Override
		public void run() {
			final int listWidth = getWidth();
			final int firstPos = mFirstPosition;

			switch (mMode) {
			case MOVE_DOWN_POS: {
				final int lastViewIndex = getChildCount() - 1;
				final int lastPos = firstPos + lastViewIndex;

				if (lastViewIndex < 0) {
					return;
				}

				if (lastPos == mLastSeenPos) {
					// No new views, let things keep going.
					mViewHelper.postOnAnimation(this);
					return;
				}

				final View lastView = getChildAt(lastViewIndex);
				final int lastViewWidth = lastView.getWidth();
				final int lastViewLeft = lastView.getLeft();
				final int lastViewPixelsShowing = listWidth - lastViewLeft;
				final int extraScroll = lastPos < mItemCount - 1 ? Math.max(
						mListPadding.right, mExtraScroll) : mListPadding.right;

				final int scrollBy = lastViewWidth - lastViewPixelsShowing
						+ extraScroll;
				smoothScrollBy(scrollBy, mScrollDuration, true);

				mLastSeenPos = lastPos;
				if (lastPos < mTargetPos) {
					mViewHelper.postOnAnimation(this);
				}
				break;
			}

			case MOVE_DOWN_BOUND: {
				final int nextViewIndex = 1;
				final int childCount = getChildCount();

				if (firstPos == mBoundPos || childCount <= nextViewIndex
						|| firstPos + childCount >= mItemCount) {
					return;
				}
				final int nextPos = firstPos + nextViewIndex;

				if (nextPos == mLastSeenPos) {
					// No new views, let things keep going.
					mViewHelper.postOnAnimation(this);
					return;
				}

				final View nextView = getChildAt(nextViewIndex);
				final int nextViewWidth = nextView.getWidth();
				final int nextViewLeft = nextView.getLeft();
				final int extraScroll = Math.max(mListPadding.right,
						mExtraScroll);
				if (nextPos < mBoundPos) {
					smoothScrollBy(
							Math.max(0, nextViewWidth + nextViewLeft
									- extraScroll), mScrollDuration, true);

					mLastSeenPos = nextPos;

					mViewHelper.postOnAnimation(this);
				} else {
					if (nextViewLeft > extraScroll) {
						smoothScrollBy(nextViewLeft - extraScroll,
								mScrollDuration, true);
					}
				}
				break;
			}

			case MOVE_UP_POS: {
				if (firstPos == mLastSeenPos) {
					// No new views, let things keep going.
					mViewHelper.postOnAnimation(this);
					return;
				}

				final View firstView = getChildAt(0);
				if (firstView == null) {
					return;
				}
				final int firstViewLeft = firstView.getLeft();
				final int extraScroll = firstPos > 0 ? Math.max(mExtraScroll,
						mListPadding.left) : mListPadding.left;

				smoothScrollBy(firstViewLeft - extraScroll, mScrollDuration,
						true);

				mLastSeenPos = firstPos;

				if (firstPos > mTargetPos) {
					mViewHelper.postOnAnimation(this);
				}
				break;
			}

			case MOVE_UP_BOUND: {
				final int lastViewIndex = getChildCount() - 2;
				if (lastViewIndex < 0) {
					return;
				}
				final int lastPos = firstPos + lastViewIndex;

				if (lastPos == mLastSeenPos) {
					// No new views, let things keep going.
					mViewHelper.postOnAnimation(this);
					return;
				}

				final View lastView = getChildAt(lastViewIndex);
				final int lastViewWidth = lastView.getWidth();
				final int lastViewLeft = lastView.getLeft();
				final int lastViewPixelsShowing = listWidth - lastViewLeft;
				final int extraScroll = Math.max(mListPadding.left,
						mExtraScroll);
				mLastSeenPos = lastPos;
				if (lastPos > mBoundPos) {
					smoothScrollBy(-(lastViewPixelsShowing - extraScroll),
							mScrollDuration, true);
					mViewHelper.postOnAnimation(this);
				} else {
					final int right = listWidth - extraScroll;
					final int lastViewRight = lastViewLeft + lastViewWidth;
					if (right > lastViewRight) {
						smoothScrollBy(-(right - lastViewRight),
								mScrollDuration, true);
					}
				}
				break;
			}

			case MOVE_OFFSET: {
				if (mLastSeenPos == firstPos) {
					// No new views, let things keep going.
					mViewHelper.postOnAnimation(this);
					return;
				}

				mLastSeenPos = firstPos;

				final int childCount = getChildCount();
				final int position = mTargetPos;
				final int lastPos = firstPos + childCount - 1;

				int viewTravelCount = 0;
				if (position < firstPos) {
					viewTravelCount = firstPos - position + 1;
				} else if (position > lastPos) {
					viewTravelCount = position - lastPos;
				}

				// Estimate how many screens we should travel
				final float screenTravelCount = (float) viewTravelCount
						/ childCount;

				final float modifier = Math.min(Math.abs(screenTravelCount),
						1.f);
				if (position < firstPos) {
					final int distance = (int) (-getWidth() * modifier);
					final int duration = (int) (mScrollDuration * modifier);
					smoothScrollBy(distance, duration, true);
					mViewHelper.postOnAnimation(this);
				} else if (position > lastPos) {
					final int distance = (int) (getWidth() * modifier);
					final int duration = (int) (mScrollDuration * modifier);
					smoothScrollBy(distance, duration, true);
					mViewHelper.postOnAnimation(this);
				} else {
					// On-screen, just scroll.
					final int targetLeft = getChildAt(position - firstPos)
							.getLeft();
					final int distance = targetLeft - mOffsetFromLeft;
					final int duration = (int) (mScrollDuration * ((float) Math
							.abs(distance) / getWidth()));
					smoothScrollBy(distance, duration, true);
				}
				break;
			}

			default:
				break;
			}
		}
	}

	/**
	 * The amount of friction applied to flings. The default value is
	 * {@link ViewConfiguration#getScrollFriction}.
	 *
	 * @return A scalar dimensionless value representing the coefficient of
	 *         friction.
	 */
	public void setFriction(float friction) {
		if (mFlingRunnable == null) {
			mFlingRunnable = new FlingRunnable();
		}
		mFlingRunnable.mScroller.setFriction(friction);
	}

	/**
	 * Sets a scale factor for the fling velocity. The initial scale factor is
	 * 1.0.
	 *
	 * @param scale
	 *            The scale factor to multiply the velocity by.
	 */
	public void setVelocityScale(float scale) {
		mVelocityScale = scale;
	}

	/**
	 * Smoothly scroll to the specified adapter position. The view will scroll
	 * such that the indicated position is displayed.
	 *
	 * @param position
	 *            Scroll to this adapter position.
	 */
	public void smoothScrollToPosition(int position) {
		if (mPositionScroller == null) {
			mPositionScroller = new PositionScroller();
		}
		mPositionScroller.start(position);
	}

	/**
	 * Smoothly scroll to the specified adapter position. The view will scroll
	 * such that the indicated position is displayed <code>offset</code> pixels
	 * from the top edge of the view. If this is impossible, (e.g. the offset
	 * would scroll the first or last item beyond the boundaries of the list) it
	 * will get as close as possible. The scroll will take <code>duration</code>
	 * milliseconds to complete.
	 *
	 * @param position
	 *            Position to scroll to
	 * @param offset
	 *            Desired distance in pixels of <code>position</code> from the
	 *            top of the view when scrolling is finished
	 * @param duration
	 *            Number of milliseconds to use for the scroll
	 */
	public void smoothScrollToPositionFromLeft(int position, int offset,
			int duration) {
		if (mPositionScroller == null) {
			mPositionScroller = new PositionScroller();
		}
		mPositionScroller.startWithOffset(position, offset, duration);
	}

	/**
	 * Smoothly scroll to the specified adapter position. The view will scroll
	 * such that the indicated position is displayed <code>offset</code> pixels
	 * from the top edge of the view. If this is impossible, (e.g. the offset
	 * would scroll the first or last item beyond the boundaries of the list) it
	 * will get as close as possible.
	 *
	 * @param position
	 *            Position to scroll to
	 * @param offset
	 *            Desired distance in pixels of <code>position</code> from the
	 *            top of the view when scrolling is finished
	 */
	public void smoothScrollToPositionFromLeft(int position, int offset) {
		if (mPositionScroller == null) {
			mPositionScroller = new PositionScroller();
		}
		mPositionScroller.startWithOffset(position, offset);
	}

	/**
	 * Smoothly scroll to the specified adapter position. The view will scroll
	 * such that the indicated position is displayed, but it will stop early if
	 * scrolling further would scroll boundPosition out of view.
	 *
	 * @param position
	 *            Scroll to this adapter position.
	 * @param boundPosition
	 *            Do not scroll if it would move this adapter position out of
	 *            view.
	 */
	public void smoothScrollToPosition(int position, int boundPosition) {
		if (mPositionScroller == null) {
			mPositionScroller = new PositionScroller();
		}
		mPositionScroller.start(position, boundPosition);
	}

	/**
	 * Smoothly scroll by distance pixels over duration milliseconds.
	 *
	 * @param distance
	 *            Distance to scroll in pixels.
	 * @param duration
	 *            Duration of the scroll animation in milliseconds.
	 */
	public void smoothScrollBy(int distance, int duration) {
		smoothScrollBy(distance, duration, false);
	}

	public void smoothScrollBy(int distance, int duration, boolean linear) {
		if (mFlingRunnable == null) {
			mFlingRunnable = new FlingRunnable();
		}

		// No sense starting to scroll if we're not going anywhere
		final int firstPos = mFirstPosition;
		final int childCount = getChildCount();
		final int lastPos = firstPos + childCount;
		final int leftLimit = getPaddingLeft();
		final int rightLimit = getWidth() - getPaddingRight();

		if (distance == 0
				|| mItemCount == 0
				|| childCount == 0
				|| (firstPos == 0 && getChildAt(0).getLeft() == leftLimit && distance < 0)
				|| (lastPos == mItemCount
						&& getChildAt(childCount - 1).getRight() == rightLimit && distance > 0)) {
			mFlingRunnable.endFling();
			if (mPositionScroller != null) {
				mPositionScroller.stop();
			}
		} else {
			reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
			mFlingRunnable.startScroll(distance, duration, linear);
		}
	}

	/**
	 * Allows RemoteViews to scroll relatively to a position.
	 */
	protected void smoothScrollByOffset(int position) {
		int index = -1;
		if (position < 0) {
			index = getFirstVisiblePosition();
		} else if (position > 0) {
			index = getLastVisiblePosition();
		}

		if (index > -1) {
			View child = getChildAt(index - getFirstVisiblePosition());
			if (child != null) {
				Rect visibleRect = new Rect();
				if (child.getGlobalVisibleRect(visibleRect)) {
					// the child is partially visible
					int childRectArea = child.getWidth() * child.getHeight();
					int visibleRectArea = visibleRect.width()
							* visibleRect.height();
					float visibleArea = (visibleRectArea / (float) childRectArea);
					final float visibleThreshold = 0.75f;
					if ((position < 0) && (visibleArea < visibleThreshold)) {
						// the top index is not perceivably visible so offset
						// to account for showing that top index as well
						++index;
					} else if ((position > 0)
							&& (visibleArea < visibleThreshold)) {
						// the bottom index is not perceivably visible so offset
						// to account for showing that bottom index as well
						--index;
					}
				}
				smoothScrollToPosition(Math.max(0,
						Math.min(getCount(), index + position)));
			}
		}
	}

	private void createScrollingCache() {
		if (mScrollingCacheEnabled && !mCachingStarted
				&& !mViewHelper.isHardwareAccelerated()) {
			setChildrenDrawnWithCacheEnabled(true);
			setChildrenDrawingCacheEnabled(true);
			mCachingStarted = mCachingActive = true;
		}
	}

	private void clearScrollingCache() {
		if (!mViewHelper.isHardwareAccelerated()) {
			if (mClearScrollingCache == null) {
				mClearScrollingCache = new Runnable() {

					@Override
					public void run() {
						if (mCachingStarted) {
							mCachingStarted = mCachingActive = false;
							setChildrenDrawnWithCacheEnabled(false);
							if ((getPersistentDrawingCache() & PERSISTENT_SCROLLING_CACHE) == 0) {
								setChildrenDrawingCacheEnabled(false);
							}
							if (!isAlwaysDrawnWithCacheEnabled()) {
								invalidate();
							}
						}
					}
				};
			}
			post(mClearScrollingCache);
		}
	}

	/**
	 * Track a motion scroll
	 *
	 * @param deltaY
	 *            Amount to offset mMotionView. This is the accumulated delta
	 *            since the motion began. Positive numbers mean the user's
	 *            finger is moving down the screen.
	 * @param incrementalDeltaY
	 *            Change in deltaY from the previous event.
	 * @return true if we're already at the beginning/end of the list and have
	 *         nothing to do.
	 */
	boolean trackMotionScroll(int deltaX, int incrementalDeltaX) {
		final int childCount = getChildCount();
		if (childCount == 0) {
			return true;
		}

		final int firstLeft = getChildAt(0).getLeft();
		final int lastRight = getChildAt(childCount - 1).getRight();

		final Rect listPadding = mListPadding;

		// "effective padding" In this case is the amount of padding that
		// affects
		// how much space should not be filled by items. If we don't clip to
		// padding
		// there is no effective padding.
		int effectivePaddingLeft = 0;
		int effectivePaddingRight = 0;

		/*
		 * TODO: check this if ((mGroupFlags & CLIP_TO_PADDING_MASK) ==
		 * CLIP_TO_PADDING_MASK) { effectivePaddingTop = listPadding.top;
		 * effectivePaddingBottom = listPadding.bottom; }
		 */

		// FIXME account for grid vertical spacing too?
		final int spaceBefore = effectivePaddingLeft - firstLeft;
		final int end = getWidth() - effectivePaddingRight;
		final int spaceAfter = lastRight - end;

		final int width = getWidth() - getPaddingRight() - getPaddingLeft();
		if (deltaX < 0) {
			deltaX = Math.max(-(width - 1), deltaX);
		} else {
			deltaX = Math.min(width - 1, deltaX);
		}

		if (incrementalDeltaX < 0) {
			incrementalDeltaX = Math.max(-(width - 1), incrementalDeltaX);
		} else {
			incrementalDeltaX = Math.min(width - 1, incrementalDeltaX);
		}

		final int firstPosition = mFirstPosition;

		// Update our guesses for where the first and last views are
		if (firstPosition == 0) {
			mFirstPositionDistanceGuess = firstLeft - listPadding.left;
		} else {
			mFirstPositionDistanceGuess += incrementalDeltaX;
		}
		if (firstPosition + childCount == mItemCount) {
			mLastPositionDistanceGuess = lastRight + listPadding.right;
		} else {
			mLastPositionDistanceGuess += incrementalDeltaX;
		}

		final boolean cannotScrollRight = (firstPosition == 0
				&& firstLeft >= listPadding.left && incrementalDeltaX >= 0);
		final boolean cannotScrollLeft = (firstPosition + childCount == mItemCount
				&& lastRight <= getWidth() - listPadding.right && incrementalDeltaX <= 0);

		if (cannotScrollRight || cannotScrollLeft) {
			return incrementalDeltaX != 0;
		}

		final boolean down = incrementalDeltaX < 0;

		final boolean inTouchMode = isInTouchMode();
		if (inTouchMode) {
			hideSelector();
		}

		final int headerViewsCount = getHeaderViewsCount();
		final int footerViewsStart = mItemCount - getFooterViewsCount();

		int start = 0;
		int count = 0;

		if (down) {
			int top = -incrementalDeltaX;

			/*
			 * TODO: check this if ((mGroupFlags & CLIP_TO_PADDING_MASK) ==
			 * CLIP_TO_PADDING_MASK) { top += listPadding.top; }
			 */

			for (int i = 0; i < childCount; i++) {
				final View child = getChildAt(i);
				if (child.getRight() >= top) {
					break;
				} else {
					count++;
					int position = firstPosition + i;
					if (position >= headerViewsCount
							&& position < footerViewsStart) {
						mRecycler.addScrapView(child, position);
					}
				}
			}
		} else {
			int bottom = getWidth() - incrementalDeltaX;

			/*
			 * TODO: check this if ((mGroupFlags & CLIP_TO_PADDING_MASK) ==
			 * CLIP_TO_PADDING_MASK) { bottom -= listPadding.bottom; }
			 */
			for (int i = childCount - 1; i >= 0; i--) {
				final View child = getChildAt(i);
				if (child.getLeft() <= bottom) {
					break;
				} else {
					start = i;
					count++;
					int position = firstPosition + i;
					if (position >= headerViewsCount
							&& position < footerViewsStart) {
						mRecycler.addScrapView(child, position);
					}
				}
			}
		}

		mMotionViewNewLeft = mMotionViewOriginalLeft + deltaX;

		mBlockLayoutRequests = true;

		if (count > 0) {
			detachViewsFromParent(start, count);
			mRecycler.removeSkippedScrap();
		}

		// invalidate before moving the children to avoid unnecessary invalidate
		// calls to bubble up from the children all the way to the top
		if (!awakenScrollBars()) {
			invalidate();
		}

		offsetChildrenLeftAndRight(incrementalDeltaX);

		if (down) {
			mFirstPosition += count;
		}

		final int absIncrementalDeltaX = Math.abs(incrementalDeltaX);
		if (spaceBefore < absIncrementalDeltaX
				|| spaceAfter < absIncrementalDeltaX) {
			fillGap(down);
		}

		if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
			final int childIndex = mSelectedPosition - mFirstPosition;
			if (childIndex >= 0 && childIndex < getChildCount()) {
				positionSelector(mSelectedPosition, getChildAt(childIndex));
			}
		} else if (mSelectorPosition != INVALID_POSITION) {
			final int childIndex = mSelectorPosition - mFirstPosition;
			if (childIndex >= 0 && childIndex < getChildCount()) {
				positionSelector(INVALID_POSITION, getChildAt(childIndex));
			}
		} else {
			mSelectorRect.setEmpty();
		}

		mBlockLayoutRequests = false;

		invokeOnItemScrollListener();

		return false;
	}

	/**
	 * Why the hell Google guys set public methods hidden??? fuck you
	 */
	public void offsetChildrenLeftAndRight(int offset) {
		final int count = getChildCount();

		for (int i = 0; i < count; i++) {
			final View v = getChildAt(i);
			v.offsetLeftAndRight(offset);
		}
	}

	/**
	 * Returns the number of header views in the list. Header views are special
	 * views at the top of the list that should not be recycled during a layout.
	 *
	 * @return The number of header views, 0 in the default implementation.
	 */
	protected int getHeaderViewsCount() {
		return 0;
	}

	/**
	 * Returns the number of footer views in the list. Footer views are special
	 * views at the bottom of the list that should not be recycled during a
	 * layout.
	 *
	 * @return The number of footer views, 0 in the default implementation.
	 */
	protected int getFooterViewsCount() {
		return 0;
	}

	/**
	 * Fills the gap left open by a touch-scroll. During a touch scroll,
	 * children that remain on screen are shifted and the other ones are
	 * discarded. The role of this method is to fill the gap thus created by
	 * performing a partial layout in the empty space.
	 *
	 * @param down
	 *            true if the scroll is going after, false if it is going up
	 */
	protected abstract void fillGap(boolean after);

	protected void hideSelector() {
		if (mSelectedPosition != INVALID_POSITION) {
			if (mLayoutMode != LAYOUT_SPECIFIC) {
				mResurrectToPosition = mSelectedPosition;
			}
			if (mNextSelectedPosition >= 0
					&& mNextSelectedPosition != mSelectedPosition) {
				mResurrectToPosition = mNextSelectedPosition;
			}
			setSelectedPositionInt(INVALID_POSITION);
			setNextSelectedPositionInt(INVALID_POSITION);
			mSelectedLeft = 0;
		}
	}

	/**
	 * @return A position to select. First we try mSelectedPosition. If that has
	 *         been clobbered by entering touch mode, we then try
	 *         mResurrectToPosition. Values are pinned to the range of items
	 *         available in the adapter
	 */
	protected int reconcileSelectedPosition() {
		int position = mSelectedPosition;
		if (position < 0) {
			position = mResurrectToPosition;
		}
		position = Math.max(0, position);
		position = Math.min(position, mItemCount - 1);
		return position;
	}

	/**
	 * Find the col closest to x. This col will be used as the motion col when
	 * scrolling
	 *
	 * @param y
	 *            Where the user touched
	 * @return The position of the first (or only) item in the col containing x
	 */
	protected abstract int findMotionCol(int x);

	/**
	 * Find the col closest to x. This col will be used as the motion col when
	 * scrolling.
	 *
	 * @param x
	 *            Where the user touched
	 * @return The position of the first (or only) item in the col closest to x
	 */
	protected int findClosestMotionCol(int x) {
		final int childCount = getChildCount();
		if (childCount == 0) {
			return INVALID_POSITION;
		}

		final int motionCol = findMotionCol(x);
		return motionCol != INVALID_POSITION ? motionCol : mFirstPosition
				+ childCount - 1;
	}

	/**
	 * Causes all the views to be rebuilt and redrawn.
	 */
	public void invalidateViews() {
		mDataChanged = true;
		rememberSyncState();
		requestLayout();
		invalidate();
	}

	/**
	 * If there is a selection returns false. Otherwise resurrects the selection
	 * and returns true if resurrected.
	 */
	protected boolean resurrectSelectionIfNeeded() {
		if (mSelectedPosition < 0 && resurrectSelection()) {
			updateSelectorState();
			return true;
		}
		return false;
	}

	/**
	 * Makes the item at the supplied position selected.
	 *
	 * @param position
	 *            the position of the new selection
	 */
	public abstract void setSelectionInt(int position);

	/**
	 * Attempt to bring the selection back if the user is switching from touch
	 * to trackball mode
	 *
	 * @return Whether selection was set to something.
	 */
	boolean resurrectSelection() {
		final int childCount = getChildCount();

		if (childCount <= 0) {
			return false;
		}

		int selectedLeft = 0;
		int selectedPos;
		int childrenLeft = mListPadding.left;
		int childrenRight = getRight() - getLeft() - mListPadding.right;
		final int firstPosition = mFirstPosition;
		final int toPosition = mResurrectToPosition;
		boolean down = true;

		if (toPosition >= firstPosition
				&& toPosition < firstPosition + childCount) {
			selectedPos = toPosition;

			final View selected = getChildAt(selectedPos - mFirstPosition);
			selectedLeft = selected.getLeft();
			int selectedRight = selected.getRight();

			// We are scrolled, don't get in the fade
			if (selectedLeft < childrenLeft) {
				selectedLeft = childrenLeft + getHorizontalFadingEdgeLength();
			} else if (selectedRight > childrenRight) {
				selectedLeft = childrenRight - selected.getMeasuredWidth()
						- getHorizontalFadingEdgeLength();
			}
		} else {
			if (toPosition < firstPosition) {
				// Default to selecting whatever is first
				selectedPos = firstPosition;
				for (int i = 0; i < childCount; i++) {
					final View v = getChildAt(i);
					final int left = v.getLeft();

					if (i == 0) {
						// Remember the position of the first item
						selectedLeft = left;
						// See if we are scrolled at all
						if (firstPosition > 0 || left < childrenLeft) {
							// If we are scrolled, don't select anything that is
							// in the fade region
							childrenLeft += getHorizontalFadingEdgeLength();
						}
					}
					if (left >= childrenLeft) {
						// Found a view whose top is fully visisble
						selectedPos = firstPosition + i;
						selectedLeft = left;
						break;
					}
				}
			} else {
				final int itemCount = mItemCount;
				down = false;
				selectedPos = firstPosition + childCount - 1;

				for (int i = childCount - 1; i >= 0; i--) {
					final View v = getChildAt(i);
					final int left = v.getLeft();
					final int right = v.getRight();

					if (i == childCount - 1) {
						selectedLeft = left;
						if (firstPosition + childCount < itemCount
								|| right > childrenRight) {
							childrenRight -= getHorizontalFadingEdgeLength();
						}
					}

					if (right <= childrenRight) {
						selectedPos = firstPosition + i;
						selectedLeft = left;
						break;
					}
				}
			}
		}

		mResurrectToPosition = INVALID_POSITION;
		removeCallbacks(mFlingRunnable);
		if (mPositionScroller != null) {
			mPositionScroller.stop();
		}
		mTouchMode = TOUCH_MODE_REST;
		clearScrollingCache();
		mSpecificLeft = selectedLeft;
		selectedPos = lookForSelectablePosition(selectedPos, down);
		if (selectedPos >= firstPosition
				&& selectedPos <= getLastVisiblePosition()) {
			mLayoutMode = LAYOUT_SPECIFIC;
			updateSelectorState();
			setSelectionInt(selectedPos);
			invokeOnItemScrollListener();
		} else {
			selectedPos = INVALID_POSITION;
		}
		reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);

		return selectedPos >= 0;
	}

	void confirmCheckedPositionsById() {
		if (LOG_ENABLED) {
			Log.i(TAG, "confirmCheckedPositionsById");
		}
		// Clear out the positional check states, we'll rebuild it below from
		// IDs.
		mCheckStates.clear();

		boolean checkedCountChanged = false;
		for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
			final long id = mCheckedIdStates.keyAt(checkedIndex);
			final int lastPos = mCheckedIdStates.valueAt(checkedIndex);

			final long lastPosId = mAdapter.getItemId(lastPos);
			if (id != lastPosId) {
				// Look around to see if the ID is nearby. If not, uncheck it.
				final int start = Math.max(0, lastPos
						- CHECK_POSITION_SEARCH_DISTANCE);
				final int end = Math.min(lastPos
						+ CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
				boolean found = false;
				for (int searchPos = start; searchPos < end; searchPos++) {
					final long searchId = mAdapter.getItemId(searchPos);
					if (id == searchId) {
						found = true;
						mCheckStates.put(searchPos, true);
						mCheckedIdStates.setValueAt(checkedIndex, searchPos);
						break;
					}
				}

				if (!found) {
					mCheckedIdStates.delete(id);
					checkedIndex--;
					mCheckedItemCount--;
					checkedCountChanged = true;

					if (android.os.Build.VERSION.SDK_INT > 11) {
						if (mChoiceActionMode != null
								&& mMultiChoiceModeCallback != null) {
							((MultiChoiceModeWrapper) mMultiChoiceModeCallback)
									.onItemCheckedStateChanged(
											(ActionMode) mChoiceActionMode,
											lastPos, id, false);
						}
					}
				}
			} else {
				mCheckStates.put(lastPos, true);
			}
		}

		if (checkedCountChanged && mChoiceActionMode != null
				&& android.os.Build.VERSION.SDK_INT > 11) {
			((ActionMode) mChoiceActionMode).invalidate();
		}
	}

	@Override
	protected void handleDataChanged() {
		if (LOG_ENABLED) {
			Log.i(TAG, "handleDataChanged");
		}
		int count = mItemCount;
		int lastHandledItemCount = mLastHandledItemCount;
		mLastHandledItemCount = mItemCount;

		if (mChoiceMode != AbsListView.CHOICE_MODE_NONE && mAdapter != null
				&& mAdapter.hasStableIds()) {
			confirmCheckedPositionsById();
		}

		// TODO: In the future we can recycle these views based on stable ID
		// instead.
		mRecycler.clearTransientStateViews();

		if (count > 0) {
			int newPos;
			int selectablePos;

			// Find the col we are supposed to sync to
			if (mNeedSync) {
				// Update this first, since setNextSelectedPositionInt inspects
				// it
				mNeedSync = false;
				mPendingSync = null;

				if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
					mLayoutMode = LAYOUT_FORCE_RIGHT;
					return;
				} else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
					if (mForceTranscriptScroll) {
						mForceTranscriptScroll = false;
						mLayoutMode = LAYOUT_FORCE_RIGHT;
						return;
					}
					final int childCount = getChildCount();
					final int listRight = getWidth() - getPaddingRight();
					final View lastChild = getChildAt(childCount - 1);
					final int lastRight = lastChild != null ? lastChild
							.getBottom() : listRight;
					if (mFirstPosition + childCount >= lastHandledItemCount
							&& lastRight <= listRight) {
						mLayoutMode = LAYOUT_FORCE_RIGHT;
						return;
					}
					// Something new came in and we didn't scroll; give the user
					// a clue that
					// there's something new.
					awakenScrollBars();
				}

				switch (mSyncMode) {
				case SYNC_SELECTED_POSITION:
					if (isInTouchMode()) {
						// We saved our state when not in touch mode. (We know
						// this because
						// mSyncMode is SYNC_SELECTED_POSITION.) Now we are
						// trying to
						// restore in touch mode. Just leave mSyncPosition as it
						// is (possibly
						// adjusting if the available range changed) and return.
						mLayoutMode = LAYOUT_SYNC;
						mSyncPosition = Math.min(Math.max(0, mSyncPosition),
								count - 1);

						return;
					} else {
						// See if we can find a position in the new data with
						// the same
						// id as the old selection. This will change
						// mSyncPosition.
						newPos = findSyncPosition();
						if (newPos >= 0) {
							// Found it. Now verify that new selection is still
							// selectable
							selectablePos = lookForSelectablePosition(newPos,
									true);
							if (selectablePos == newPos) {
								// Same col id is selected
								mSyncPosition = newPos;

								if (mSyncWidth == getWidth()) {
									// If we are at the same height as when we
									// saved state, try
									// to restore the scroll position too.
									mLayoutMode = LAYOUT_SYNC;
								} else {
									// We are not the same height as when the
									// selection was saved, so
									// don't try to restore the exact position
									mLayoutMode = LAYOUT_SET_SELECTION;
								}

								// Restore selection
								setNextSelectedPositionInt(newPos);
								return;
							}
						}
					}
					break;
				case SYNC_FIRST_POSITION:
					// Leave mSyncPosition as it is -- just pin to available
					// range
					mLayoutMode = LAYOUT_SYNC;
					mSyncPosition = Math.min(Math.max(0, mSyncPosition),
							count - 1);

					return;
				}
			}

			if (!isInTouchMode()) {
				// We couldn't find matching data -- try to use the same
				// position
				newPos = getSelectedItemPosition();

				// Pin position to the available range
				if (newPos >= count) {
					newPos = count - 1;
				}
				if (newPos < 0) {
					newPos = 0;
				}

				// Make sure we select something selectable -- first look down
				selectablePos = lookForSelectablePosition(newPos, true);

				if (selectablePos >= 0) {
					setNextSelectedPositionInt(selectablePos);
					return;
				} else {
					// Looking down didn't work -- try looking up
					selectablePos = lookForSelectablePosition(newPos, false);
					if (selectablePos >= 0) {
						setNextSelectedPositionInt(selectablePos);
						return;
					}
				}
			} else {

				// We already know where we want to resurrect the selection
				if (mResurrectToPosition >= 0) {
					return;
				}
			}

		}

		// Nothing is selected. Give up and reset everything.
		mLayoutMode = mStackFromRight ? LAYOUT_FORCE_RIGHT : LAYOUT_FORCE_LEFT;
		mSelectedPosition = INVALID_POSITION;
		mSelectedColId = INVALID_COL_ID;
		mNextSelectedPosition = INVALID_POSITION;
		mNextSelectedColId = INVALID_COL_ID;
		mNeedSync = false;
		mPendingSync = null;
		mSelectorPosition = INVALID_POSITION;
		checkSelectionChanged();
	}

	/**
	 * What is the distance between the source and destination rectangles given
	 * the direction of focus navigation between them? The direction basically
	 * helps figure out more quickly what is self evident by the relationship
	 * between the rects...
	 *
	 * @param source
	 *            the source rectangle
	 * @param dest
	 *            the destination rectangle
	 * @param direction
	 *            the direction
	 * @return the distance between the rectangles
	 */
	public static int getDistance(Rect source, Rect dest, int direction) {

		// TODO: implement this

		int sX, sY; // source x, y
		int dX, dY; // dest x, y
		switch (direction) {
		case View.FOCUS_RIGHT:
			sX = source.right;
			sY = source.top + source.height() / 2;
			dX = dest.left;
			dY = dest.top + dest.height() / 2;
			break;
		case View.FOCUS_DOWN:
			sX = source.left + source.width() / 2;
			sY = source.bottom;
			dX = dest.left + dest.width() / 2;
			dY = dest.top;
			break;
		case View.FOCUS_LEFT:
			sX = source.left;
			sY = source.top + source.height() / 2;
			dX = dest.right;
			dY = dest.top + dest.height() / 2;
			break;
		case View.FOCUS_UP:
			sX = source.left + source.width() / 2;
			sY = source.top;
			dX = dest.left + dest.width() / 2;
			dY = dest.bottom;
			break;
		case View.FOCUS_FORWARD:
		case View.FOCUS_BACKWARD:
			sX = source.right + source.width() / 2;
			sY = source.top + source.height() / 2;
			dX = dest.left + dest.width() / 2;
			dY = dest.top + dest.height() / 2;
			break;
		default:
			throw new IllegalArgumentException("direction must be one of "
					+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
					+ "FOCUS_FORWARD, FOCUS_BACKWARD}.");
		}
		int deltaX = dX - sX;
		int deltaY = dY - sY;
		return deltaY * deltaY + deltaX * deltaX;
	}

	/**
	 * Return an InputConnection for editing of the filter text.
	 */
	@Override
	public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
		return null;
	}

	/**
	 * For filtering we proxy an input connection to an internal text editor,
	 * and this allows the proxying to happen.
	 */
	@Override
	public boolean checkInputConnectionProxy(View view) {
		return false;
	}

	@Override
	protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
		return new AbsHListView.LayoutParams(
				ViewGroup.LayoutParams.WRAP_CONTENT,
				ViewGroup.LayoutParams.MATCH_PARENT, 0);
	}

	@Override
	protected ViewGroup.LayoutParams generateLayoutParams(
			ViewGroup.LayoutParams p) {
		return new LayoutParams(p);
	}

	@Override
	public LayoutParams generateLayoutParams(AttributeSet attrs) {
		return new AbsHListView.LayoutParams(getContext(), attrs);
	}

	@Override
	protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
		return p instanceof AbsHListView.LayoutParams;
	}

	/**
	 * Puts the list or grid into transcript mode. In this mode the list or grid
	 * will always scroll to the bottom to show new items.
	 *
	 * @param mode
	 *            the transcript mode to set
	 *
	 * @see #TRANSCRIPT_MODE_DISABLED
	 * @see #TRANSCRIPT_MODE_NORMAL
	 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
	 */
	public void setTranscriptMode(int mode) {
		mTranscriptMode = mode;
	}

	/**
	 * Returns the current transcript mode.
	 *
	 * @return {@link #TRANSCRIPT_MODE_DISABLED},
	 *         {@link #TRANSCRIPT_MODE_NORMAL} or
	 *         {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
	 */
	public int getTranscriptMode() {
		return mTranscriptMode;
	}

	@Override
	public int getSolidColor() {
		return mCacheColorHint;
	}

	/**
	 * When set to a non-zero value, the cache color hint indicates that this
	 * list is always drawn on top of a solid, single-color, opaque background.
	 *
	 * Zero means that what's behind this object is translucent (non solid) or
	 * is not made of a single color. This hint will not affect any existing
	 * background drawable set on this view ( typically set via
	 * {@link #setBackgroundDrawable(Drawable)}).
	 *
	 * @param color
	 *            The background color
	 */
	public void setCacheColorHint(int color) {
		if (color != mCacheColorHint) {
			mCacheColorHint = color;
			int count = getChildCount();
			for (int i = 0; i < count; i++) {
				getChildAt(i).setDrawingCacheBackgroundColor(color);
			}
			mRecycler.setCacheColorHint(color);
		}
	}

	/**
	 * When set to a non-zero value, the cache color hint indicates that this
	 * list is always drawn on top of a solid, single-color, opaque background
	 *
	 * @return The cache color hint
	 */
	@ViewDebug.ExportedProperty(category = "drawing")
	public int getCacheColorHint() {
		return mCacheColorHint;
	}

	/**
	 * Move all views (excluding headers and footers) held by this AbsListView
	 * into the supplied List. This includes views displayed on the screen as
	 * well as views stored in AbsListView's internal view recycler.
	 *
	 * @param views
	 *            A list into which to put the reclaimed views
	 */
	@SuppressLint("NewApi")
	public void reclaimViews(List<View> views) {
		int childCount = getChildCount();
		RecyclerListener listener = mRecycler.mRecyclerListener;

		// Reclaim views on screen
		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			AbsHListView.LayoutParams lp = (AbsHListView.LayoutParams) child
					.getLayoutParams();
			// Don't reclaim header or footer views, or views that should be
			// ignored
			if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
				views.add(child);

				if (android.os.Build.VERSION.SDK_INT >= 14) {
					child.setAccessibilityDelegate(null);
				}

				if (listener != null) {
					// Pretend they went through the scrap heap
					listener.onMovedToScrapHeap(child);
				}
			}
		}
		mRecycler.reclaimScrapViews(views);
		removeAllViewsInLayout();
	}

	private void finishGlows() {
		if (mEdgeGlowTop != null) {
			mEdgeGlowTop.finish();
			mEdgeGlowBottom.finish();
		}
	}

	/**
	 * Hints the RemoteViewsAdapter, if it exists, about which views are
	 * currently being displayed by the AbsListView.
	 */
	protected void setVisibleRangeHint(int start, int end) {
	}

	/**
	 * Sets the recycler listener to be notified whenever a View is set aside in
	 * the recycler for later reuse. This listener can be used to free resources
	 * associated to the View.
	 *
	 * @param listener
	 *            The recycler listener to be notified of views set aside in the
	 *            recycler.
	 *
	 * @see it.sephiroth.android.library.widget.AbsHListView.RecycleBin
	 * @see it.sephiroth.android.library.widget.AbsHListView.RecyclerListener
	 */
	public void setRecyclerListener(RecyclerListener listener) {
		mRecycler.mRecyclerListener = listener;
	}

	public class AdapterDataSetObserver extends
			AdapterView<ListAdapter>.AdapterDataSetObserver {

		@Override
		public void onChanged() {
			if (LOG_ENABLED) {
				Log.i(TAG, "AdapterDataSetObserver::onChanged");
			}
			super.onChanged();
		}

		@Override
		public void onInvalidated() {
			if (LOG_ENABLED) {
				Log.i(TAG, "AdapterDataSetObserver::onInvalidated");
			}
			super.onInvalidated();
		}
	}

	/**
	 * AbsListView extends LayoutParams to provide a place to hold the view
	 * type.
	 */
	public static class LayoutParams extends ViewGroup.LayoutParams {

		/**
		 * View type for this view, as returned by
		 * {@link android.widget.Adapter#getItemViewType(int) }
		 */
		public int viewType;

		/**
		 * When this boolean is set, the view has been added to the AbsListView
		 * at least once. It is used to know whether headers/footers have
		 * already been added to the list view and whether they should be
		 * treated as recycled views or not.
		 */
		public boolean recycledHeaderFooter;

		/**
		 * When an AbsListView is measured with an AT_MOST measure spec, it
		 * needs to obtain children views to measure itself. When doing so, the
		 * children are not attached to the window, but put in the recycler
		 * which assumes they've been attached before. Setting this flag will
		 * force the reused view to be attached to the window rather than just
		 * attached to the parent.
		 */
		public boolean forceAdd;

		/**
		 * The position the view was removed from when pulled out of the scrap
		 * heap.
		 *
		 */
		public int scrappedFromPosition;

		/**
		 * The ID the view represents
		 */
		public long itemId = -1;

		public LayoutParams(Context c, AttributeSet attrs) {
			super(c, attrs);
		}

		public LayoutParams(int w, int h) {
			super(w, h);
		}

		public LayoutParams(int w, int h, int viewType) {
			super(w, h);
			this.viewType = viewType;
		}

		public LayoutParams(ViewGroup.LayoutParams source) {
			super(source);
		}
	}

	/**
	 * A RecyclerListener is used to receive a notification whenever a View is
	 * placed inside the RecycleBin's scrap heap. This listener is used to free
	 * resources associated to Views placed in the RecycleBin.
	 *
	 * @see it.sephiroth.android.library.widget.AbsHListView.RecycleBin
	 * @see it.sephiroth.android.library.widget.AbsHListView#setRecyclerListener(it.sephiroth.android.library.widget.AbsHListView.RecyclerListener)
	 */
	public static interface RecyclerListener {

		/**
		 * Indicates that the specified View was moved into the recycler's scrap
		 * heap. The view is not displayed on screen any more and any expensive
		 * resource associated with the view should be discarded.
		 *
		 * @param view
		 */
		void onMovedToScrapHeap(View view);
	}

	/**
	 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin
	 * has two levels of storage: ActiveViews and ScrapViews. ActiveViews are
	 * those views which were onscreen at the start of a layout. By
	 * construction, they are displaying current information. At the end of
	 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews
	 * are old views that could potentially be used by the adapter to avoid
	 * allocating views unnecessarily.
	 *
	 * @see it.sephiroth.android.library.widget.AbsHListView#setRecyclerListener(it.sephiroth.android.library.widget.AbsHListView.RecyclerListener)
	 * @see it.sephiroth.android.library.widget.AbsHListView.RecyclerListener
	 */
	public class RecycleBin {

		private RecyclerListener mRecyclerListener;

		/**
		 * The position of the first view stored in mActiveViews.
		 */
		private int mFirstActivePosition;

		/**
		 * Views that were on screen at the start of layout. This array is
		 * populated at the start of layout, and at the end of layout all view
		 * in mActiveViews are moved to mScrapViews. Views in mActiveViews
		 * represent a contiguous range of Views, with position of the first
		 * view store in mFirstActivePosition.
		 */
		private View[] mActiveViews = new View[0];

		/**
		 * Unsorted views that can be used by the adapter as a convert view.
		 */
		private ArrayList<View>[] mScrapViews;

		private int mViewTypeCount;

		private ArrayList<View> mCurrentScrap;

		private ArrayList<View> mSkippedScrap;

		private SparseArrayCompat<View> mTransientStateViews;

		@SuppressWarnings("unchecked")
		public void setViewTypeCount(int viewTypeCount) {
			if (viewTypeCount < 1) {
				throw new IllegalArgumentException(
						"Can't have a viewTypeCount < 1");
			}
			// noinspection unchecked
			ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
			for (int i = 0; i < viewTypeCount; i++) {
				scrapViews[i] = new ArrayList<View>();
			}
			mViewTypeCount = viewTypeCount;
			mCurrentScrap = scrapViews[0];
			mScrapViews = scrapViews;
		}

		public void markChildrenDirty() {
			if (mViewTypeCount == 1) {
				final ArrayList<View> scrap = mCurrentScrap;
				final int scrapCount = scrap.size();
				for (int i = 0; i < scrapCount; i++) {
					scrap.get(i).forceLayout();
				}
			} else {
				final int typeCount = mViewTypeCount;
				for (int i = 0; i < typeCount; i++) {
					final ArrayList<View> scrap = mScrapViews[i];
					final int scrapCount = scrap.size();
					for (int j = 0; j < scrapCount; j++) {
						scrap.get(j).forceLayout();
					}
				}
			}
			if (mTransientStateViews != null) {
				final int count = mTransientStateViews.size();
				for (int i = 0; i < count; i++) {
					mTransientStateViews.valueAt(i).forceLayout();
				}
			}
		}

		public boolean shouldRecycleViewType(int viewType) {
			return viewType >= 0;
		}

		/**
		 * Clears the scrap heap.
		 */
		public void clear() {
			if (mViewTypeCount == 1) {
				final ArrayList<View> scrap = mCurrentScrap;
				final int scrapCount = scrap.size();
				for (int i = 0; i < scrapCount; i++) {
					removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
				}
			} else {
				final int typeCount = mViewTypeCount;
				for (int i = 0; i < typeCount; i++) {
					final ArrayList<View> scrap = mScrapViews[i];
					final int scrapCount = scrap.size();
					for (int j = 0; j < scrapCount; j++) {
						removeDetachedView(scrap.remove(scrapCount - 1 - j),
								false);
					}
				}
			}
			if (mTransientStateViews != null) {
				mTransientStateViews.clear();
			}
		}

		/**
		 * Fill ActiveViews with all of the children of the AbsListView.
		 *
		 * @param childCount
		 *            The minimum number of views mActiveViews should hold
		 * @param firstActivePosition
		 *            The position of the first view that will be stored in
		 *            mActiveViews
		 */
		public void fillActiveViews(int childCount, int firstActivePosition) {
			if (mActiveViews.length < childCount) {
				mActiveViews = new View[childCount];
			}
			mFirstActivePosition = firstActivePosition;

			final View[] activeViews = mActiveViews;
			for (int i = 0; i < childCount; i++) {
				View child = getChildAt(i);
				AbsHListView.LayoutParams lp = (AbsHListView.LayoutParams) child
						.getLayoutParams();
				// Don't put header or footer views into the scrap heap
				if (lp != null
						&& lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
					// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in
					// active views.
					// However, we will NOT place them into scrap views.
					activeViews[i] = child;
				}
			}
		}

		/**
		 * Get the view corresponding to the specified position. The view will
		 * be removed from mActiveViews if it is found.
		 *
		 * @param position
		 *            The position to look up in mActiveViews
		 * @return The view if it is found, null otherwise
		 */
		public View getActiveView(int position) {
			int index = position - mFirstActivePosition;
			final View[] activeViews = mActiveViews;
			if (index >= 0 && index < activeViews.length) {
				final View match = activeViews[index];
				activeViews[index] = null;
				return match;
			}
			return null;
		}

		View getTransientStateView(int position) {
			if (mTransientStateViews == null) {
				return null;
			}
			final int index = mTransientStateViews.indexOfKey(position);
			if (index < 0) {
				return null;
			}
			final View result = mTransientStateViews.valueAt(index);
			mTransientStateViews.removeAt(index);
			return result;
		}

		/**
		 * Dump any currently saved views with transient state.
		 */
		void clearTransientStateViews() {
			if (mTransientStateViews != null) {
				mTransientStateViews.clear();
			}
		}

		/**
		 * @return A view from the ScrapViews collection. These are unordered.
		 */
		View getScrapView(int position) {
			if (mViewTypeCount == 1) {
				return retrieveFromScrap(mCurrentScrap, position);
			} else {
				int whichScrap = mAdapter.getItemViewType(position);
				if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
					return retrieveFromScrap(mScrapViews[whichScrap], position);
				}
			}
			return null;
		}

		/**
		 * Put a view into the ScrapViews list. These views are unordered.
		 *
		 * @param scrap
		 *            The view to add
		 */
		@SuppressLint("NewApi")
		public void addScrapView(View scrap, int position) {
			AbsHListView.LayoutParams lp = (AbsHListView.LayoutParams) scrap
					.getLayoutParams();
			if (lp == null) {
				return;
			}

			lp.scrappedFromPosition = position;

			// Don't put header or footer views or views that should be ignored
			// into the scrap heap
			int viewType = lp.viewType;

			final boolean scrapHasTransientState = android.os.Build.VERSION.SDK_INT >= 16 ? scrap
					.hasTransientState() : false;

			if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
				if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER
						|| scrapHasTransientState) {
					if (mSkippedScrap == null) {
						mSkippedScrap = new ArrayList<View>();
					}
					mSkippedScrap.add(scrap);
				}
				if (scrapHasTransientState) {
					if (mTransientStateViews == null) {
						mTransientStateViews = new SparseArrayCompat<View>();
					}
					scrap.onStartTemporaryDetach();
					mTransientStateViews.put(position, scrap);
				}
				return;
			}

			scrap.onStartTemporaryDetach();
			if (mViewTypeCount == 1) {
				mCurrentScrap.add(scrap);
			} else {
				mScrapViews[viewType].add(scrap);
			}

			if (android.os.Build.VERSION.SDK_INT >= 14) {
				scrap.setAccessibilityDelegate(null);
			}

			if (mRecyclerListener != null) {
				mRecyclerListener.onMovedToScrapHeap(scrap);
			}
		}

		/**
		 * Finish the removal of any views that skipped the scrap heap.
		 */
		public void removeSkippedScrap() {
			if (mSkippedScrap == null) {
				return;
			}
			final int count = mSkippedScrap.size();
			for (int i = 0; i < count; i++) {
				removeDetachedView(mSkippedScrap.get(i), false);
			}
			mSkippedScrap.clear();
		}

		/**
		 * Move all views remaining in mActiveViews to mScrapViews.
		 */
		@SuppressLint("NewApi")
		public void scrapActiveViews() {
			final View[] activeViews = mActiveViews;
			final boolean hasListener = mRecyclerListener != null;
			final boolean multipleScraps = mViewTypeCount > 1;

			ArrayList<View> scrapViews = mCurrentScrap;
			final int count = activeViews.length;
			for (int i = count - 1; i >= 0; i--) {
				final View victim = activeViews[i];
				if (victim != null) {
					final AbsHListView.LayoutParams lp = (AbsHListView.LayoutParams) victim
							.getLayoutParams();
					int whichScrap = lp.viewType;

					activeViews[i] = null;

					final boolean scrapHasTransientState = android.os.Build.VERSION.SDK_INT >= 16 ? victim
							.hasTransientState() : false;
					if (!shouldRecycleViewType(whichScrap)
							|| scrapHasTransientState) {
						// Do not move views that should be ignored
						if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER
								|| scrapHasTransientState) {
							removeDetachedView(victim, false);
						}
						if (scrapHasTransientState) {
							if (mTransientStateViews == null) {
								mTransientStateViews = new SparseArrayCompat<View>();
							}
							mTransientStateViews.put(mFirstActivePosition + i,
									victim);
						}
						continue;
					}

					if (multipleScraps) {
						scrapViews = mScrapViews[whichScrap];
					}
					victim.onStartTemporaryDetach();
					lp.scrappedFromPosition = mFirstActivePosition + i;
					scrapViews.add(victim);

					if (android.os.Build.VERSION.SDK_INT >= 14) {
						victim.setAccessibilityDelegate(null);
					}

					if (hasListener) {
						mRecyclerListener.onMovedToScrapHeap(victim);
					}
				}
			}

			pruneScrapViews();
		}

		/**
		 * Makes sure that the size of mScrapViews does not exceed the size of
		 * mActiveViews. (This can happen if an adapter does not recycle its
		 * views).
		 */
		@SuppressLint("NewApi")
		private void pruneScrapViews() {
			final int maxViews = mActiveViews.length;
			final int viewTypeCount = mViewTypeCount;
			final ArrayList<View>[] scrapViews = mScrapViews;
			for (int i = 0; i < viewTypeCount; ++i) {
				final ArrayList<View> scrapPile = scrapViews[i];
				int size = scrapPile.size();
				final int extras = size - maxViews;
				size--;
				for (int j = 0; j < extras; j++) {
					removeDetachedView(scrapPile.remove(size--), false);
				}
			}

			if (mTransientStateViews != null) {
				for (int i = 0; i < mTransientStateViews.size(); i++) {
					final View v = mTransientStateViews.valueAt(i);

					// this code is never executed on android < 16
					if (!v.hasTransientState()) {
						mTransientStateViews.removeAt(i);
						i--;
					}
				}
			}
		}

		/**
		 * Puts all views in the scrap heap into the supplied list.
		 */
		void reclaimScrapViews(List<View> views) {
			if (mViewTypeCount == 1) {
				views.addAll(mCurrentScrap);
			} else {
				final int viewTypeCount = mViewTypeCount;
				final ArrayList<View>[] scrapViews = mScrapViews;
				for (int i = 0; i < viewTypeCount; ++i) {
					final ArrayList<View> scrapPile = scrapViews[i];
					views.addAll(scrapPile);
				}
			}
		}

		/**
		 * Updates the cache color hint of all known views.
		 *
		 * @param color
		 *            The new cache color hint.
		 */
		void setCacheColorHint(int color) {
			if (mViewTypeCount == 1) {
				final ArrayList<View> scrap = mCurrentScrap;
				final int scrapCount = scrap.size();
				for (int i = 0; i < scrapCount; i++) {
					scrap.get(i).setDrawingCacheBackgroundColor(color);
				}
			} else {
				final int typeCount = mViewTypeCount;
				for (int i = 0; i < typeCount; i++) {
					final ArrayList<View> scrap = mScrapViews[i];
					final int scrapCount = scrap.size();
					for (int j = 0; j < scrapCount; j++) {
						scrap.get(j).setDrawingCacheBackgroundColor(color);
					}
				}
			}
			// Just in case this is called during a layout pass
			final View[] activeViews = mActiveViews;
			final int count = activeViews.length;
			for (int i = 0; i < count; ++i) {
				final View victim = activeViews[i];
				if (victim != null) {
					victim.setDrawingCacheBackgroundColor(color);
				}
			}
		}
	}

	static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
		int size = scrapViews.size();
		if (size > 0) {
			// See if we still have a view for this position.
			for (int i = 0; i < size; i++) {
				View view = scrapViews.get(i);
				if (((AbsHListView.LayoutParams) view.getLayoutParams()).scrappedFromPosition == position) {
					scrapViews.remove(i);
					return view;
				}
			}
			return scrapViews.remove(size - 1);
		} else {
			return null;
		}
	}
}
